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-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": [

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,8 +4,9 @@ import ReactDOM from 'react-dom';
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 = {
isMinimalForm: boolean | undefined;
clientId: string;
@ -43,6 +44,7 @@ type PayPalButtonScriptLoaderProps = {
};
planId: string | null;
};
/* eslint-enable @typescript-eslint/naming-convention */
type PayPalButtonScriptLoaderState = {
isSdkLoaded: boolean;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -20,10 +20,10 @@ import {
getTitleFromId
} from '../../../../../utils';
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 { FullWidthRow, Link } from '../../helpers';
import TimelinePagination from './TimelinePagination';
import TimelinePagination from './timeline-pagination';
import './timeline.css';
@ -37,32 +37,15 @@ const localeCode = langCodes[clientLocale];
// Items per page in timeline.
const ITEMS_PER_PAGE = 15;
interface CompletedMap {
id: string;
completedDate: number;
challengeType: number;
solution: string;
challengeFiles: ChallengeFiles;
githubLink: string;
}
interface TimelineProps {
completedMap: CompletedMap[];
completedMap: CompletedChallenge[];
t: TFunction;
username: string;
}
interface SortedTimeline {
id: string;
completedDate: number;
challengeFiles: ChallengeFiles;
githubLink: string;
solution: string;
}
interface TimelineInnerProps extends TimelineProps {
idToNameMap: Map<string, string>;
sortedTimeline: SortedTimeline[];
sortedTimeline: CompletedChallenge[];
totalPages: number;
}
@ -83,12 +66,12 @@ function TimelineInner({
function viewSolution(
id: string,
solution_: string,
solution_: string | undefined | null,
challengeFiles_: ChallengeFiles
): void {
setSolutionToView(id);
setSolutionOpen(true);
setSolution(solution_);
setSolution(solution_ ?? '');
setChallengeFiles(challengeFiles_);
}
@ -115,8 +98,8 @@ function TimelineInner({
function renderViewButton(
id: string,
challengeFiles: ChallengeFiles,
githubLink: string,
solution: string
githubLink?: string,
solution?: string | null
): React.ReactNode {
if (challengeFiles?.length) {
return (
@ -159,7 +142,7 @@ function TimelineInner({
</DropdownButton>
</div>
);
} else if (maybeUrlRE.test(solution)) {
} else if (solution && maybeUrlRE.test(solution)) {
return (
<Button
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 completedDate = new Date(completed.completedDate);
// @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 { AvatarRenderer } from '../../helpers';
import Link from '../../helpers/link';
import SocialIcons from './SocialIcons';
import SocialIcons from './social-icons';
import './camper.css';
@ -22,7 +22,7 @@ const { clientLocale } = envData;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const localeCode = langCodes[clientLocale];
interface ICamperProps {
interface CamperProps {
about: string;
githubProfile: string;
isDonating: boolean;
@ -83,7 +83,7 @@ function Camper({
linkedin,
twitter,
website
}: ICamperProps): JSX.Element {
}: CamperProps): JSX.Element {
const { t } = useTranslation();
return (

View File

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

View File

@ -1,7 +1,7 @@
import { render, screen } from '@testing-library/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
// 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
const localeCode = langCodes[clientLocale];
interface IHeatMapProps {
interface HeatMapProps {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
calendar: any;
}
interface IPageData {
interface PageData {
startOfCalendar: Date;
endOfCalendar: Date;
}
interface ICalendarData {
interface CalendarData {
date: Date;
count: number;
}
interface IHeatMapInnerProps {
calendarData: ICalendarData[];
interface HeatMapInnerProps {
calendarData: CalendarData[];
currentStreak: number;
longestStreak: number;
pages: IPageData[];
pages: PageData[];
points?: number;
t: TFunction;
}
interface IHeatMapInnerState {
interface HeatMapInnerState {
pageIndex: number;
}
class HeatMapInner extends Component<IHeatMapInnerProps, IHeatMapInnerState> {
constructor(props: IHeatMapInnerProps) {
class HeatMapInner extends Component<HeatMapInnerProps, HeatMapInnerState> {
constructor(props: HeatMapInnerProps) {
super(props);
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();
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const { calendar } = props;
@ -202,7 +202,7 @@ const HeatMap = (props: IHeatMapProps): JSX.Element => {
let startOfCalendar;
// creates pages for heatmap
const pages: IPageData[] = [];
const pages: PageData[] = [];
do {
startOfCalendar = addDays(addMonths(endOfCalendar, -6), 1);
@ -219,7 +219,7 @@ const HeatMap = (props: IHeatMapProps): JSX.Element => {
pages.reverse();
const calendarData: ICalendarData[] = [];
const calendarData: CalendarData[] = [];
let dayCounter = pages[0].startOfCalendar;
// 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 { useTranslation } from 'react-i18next';
import type { Portfolio as PortfolioData } from '../../../redux/prop-types';
import { FullWidthRow } from '../../helpers';
import './portfolio.css';
interface IPortfolioData {
description: string;
id: string;
image: string;
title: string;
url: string;
interface PortfolioProps {
portfolio: PortfolioData[];
}
interface IPortfolioProps {
portfolio: IPortfolioData[];
}
function Portfolio({ portfolio = [] }: IPortfolioProps): JSX.Element | null {
function Portfolio({ portfolio = [] }: PortfolioProps): JSX.Element | null {
const { t } = useTranslation();
if (!portfolio.length) {
return null;

View File

@ -10,7 +10,7 @@ import React from 'react';
import { useTranslation } from 'react-i18next';
import './social-icons.css';
interface ISocialIconsProps {
interface SocialIconsProps {
email?: string;
githubProfile: string;
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 {
githubProfile,
isLinkedIn,

View File

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

View File

@ -1,12 +1,16 @@
import { render, screen } from '@testing-library/react';
import React from 'react';
import Profile from './Profile';
import Profile from './profile';
jest.mock('../../analytics');
const userProps = {
user: {
acceptedPrivacyTerms: true,
currentChallengeId: 'string',
email: 'string',
emailVerified: true,
profileUI: {
isLocked: false,
showAbout: false,
@ -26,8 +30,12 @@ const userProps = {
},
completedChallenges: [],
portfolio: [],
progressTimestamps: [],
about: 'string',
githubProfile: 'string',
isBanned: false,
isCheater: true,
isHonest: true,
isGithub: true,
isLinkedIn: true,
isTwitter: true,
@ -38,11 +46,30 @@ const userProps = {
name: 'string',
picture: 'string',
points: 1,
sendQuincyEmail: true,
sound: true,
theme: 'string',
twitter: 'string',
username: 'string',
website: 'string',
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
navigate: () => {}

View File

@ -4,52 +4,16 @@ import Helmet from 'react-helmet';
import { TFunction, useTranslation } from 'react-i18next';
import { CurrentChallengeLink, FullWidthRow, Link, Spacer } from '../helpers';
import Camper from './components/Camper';
import Certifications from './components/Certifications';
import HeatMap from './components/HeatMap';
import Portfolio from './components/Portfolio';
import { User } from './../../redux/prop-types';
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;
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;
};
user: User;
}
function renderMessage(
@ -88,7 +52,7 @@ function renderMessage(
);
}
function renderProfile(user: IProfileProps['user']): JSX.Element {
function renderProfile(user: ProfileProps['user']): JSX.Element {
const {
profileUI: {
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 {
profileUI: { isLocked = true },

View File

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

View File

@ -1,7 +1,7 @@
import { isEqual } from 'lodash-es';
import React, { Component } from 'react';
import { HotKeys, ObserveKeys } from 'react-hotkeys';
import { withTranslation } from 'react-i18next';
import { TFunction, withTranslation } from 'react-i18next';
import { Hit } from 'react-instantsearch-core';
import { SearchBox } from 'react-instantsearch-dom';
import { connect } from 'react-redux';
@ -39,23 +39,23 @@ const mapDispatchToProps = (dispatch: Dispatch<AnyAction>) =>
dispatch
);
type searchBarPropType = {
type SearchBarProps = {
innerRef?: React.RefObject<HTMLDivElement>;
toggleSearchDropdown: typeof toggleSearchDropdown;
toggleSearchFocused: typeof toggleSearchFocused;
updateSearchQuery: typeof updateSearchQuery;
isDropdownEnabled?: boolean;
isSearchFocused?: boolean;
t?: (label: string) => string;
t?: TFunction;
};
type classState = {
type SearchBarState = {
index: number;
hits: Array<Hit>;
};
export class SearchBar extends Component<searchBarPropType, classState> {
export class SearchBar extends Component<SearchBarProps, SearchBarState> {
static displayName: string;
constructor(props: searchBarPropType) {
constructor(props: SearchBarProps) {
super(props);
this.handleChange = this.handleChange.bind(this);
@ -165,18 +165,18 @@ export class SearchBar extends Component<searchBarPropType, classState> {
};
keyMap = {
INDEX_UP: ['up'],
INDEX_DOWN: ['down']
indexUp: ['up'],
indexDown: ['down']
};
keyHandlers = {
INDEX_UP: (e: KeyboardEvent | undefined): void => {
indexUp: (e: KeyboardEvent | undefined): void => {
e?.preventDefault();
this.setState(({ index, hits }) => ({
index: index === -1 ? hits.length - 1 : index - 1
}));
},
INDEX_DOWN: (e: KeyboardEvent | undefined): void => {
indexDown: (e: KeyboardEvent | undefined): void => {
e?.preventDefault();
this.setState(({ index, hits }) => ({
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';
const searchUrl = searchPageUrl;
interface customHitsPropTypes {
interface CustomHitsProps {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
hits: Array<any>;
searchQuery: string;
@ -17,7 +17,7 @@ interface customHitsPropTypes {
selectedIndex: number;
handleHits: (currHits: Array<Hit>) => void;
}
interface searchHitsPropTypes {
interface SearchHitsProps {
searchState: SearchState;
handleMouseEnter: (e: React.SyntheticEvent<HTMLElement, Event>) => void;
handleMouseLeave: (e: React.SyntheticEvent<HTMLElement, Event>) => void;
@ -32,7 +32,7 @@ const CustomHits = connectHits(
handleMouseLeave,
selectedIndex,
handleHits
}: customHitsPropTypes) => {
}: CustomHitsProps) => {
const { t } = useTranslation();
const noHits = isEmpty(hits);
const noHitsTitle = t('search.no-tutorials');
@ -101,7 +101,7 @@ const SearchHits = connectStateResults(
handleMouseLeave,
selectedIndex,
handleHits
}: searchHitsPropTypes) => {
}: SearchHitsProps) => {
return isEmpty(searchState) || !searchState.query ? null : (
<CustomHits
handleHits={handleHits}

View File

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

View File

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

View File

@ -280,10 +280,10 @@ class PortfolioSettings extends Component<PortfolioProps, PortfolioState> {
!title ||
!isURL(url, {
protocols: ['http', 'https'],
/* eslint-disable camelcase */
/* eslint-disable camelcase, @typescript-eslint/naming-convention */
require_tld: 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 { userSelector } from '../../redux';
import type { ProfileUI } from '../../redux/prop-types';
import { submitProfileUI } from '../../redux/settings';
import FullWidthRow from '../helpers/full-width-row';
@ -22,24 +23,11 @@ const mapStateToProps = createSelector(userSelector, user => ({
const mapDispatchToProps = (dispatch: 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 = {
submitProfileUI: (profileUI: ProfileUIType) => void;
submitProfileUI: (profileUI: ProfileUI) => void;
t: TFunction;
user: {
profileUI: ProfileUIType;
profileUI: ProfileUI;
username: string;
};
};
@ -56,8 +44,8 @@ function PrivacySettings({
function toggleFlag(flag: string): () => void {
return () => {
const privacyValues = { ...user.profileUI };
privacyValues[flag as keyof ProfileUIType] =
!privacyValues[flag as keyof ProfileUIType];
privacyValues[flag as keyof ProfileUI] =
!privacyValues[flag as keyof ProfileUI];
submitProfileUI(privacyValues);
};
}

View File

@ -1,32 +1,6 @@
import PropTypes from 'prop-types';
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({
about: PropTypes.string,
completedChallenges: PropTypes.arrayOf(
@ -95,8 +69,6 @@ export const StepsPropType = PropTypes.shape({
isShowProfile: PropTypes.bool
});
// TYPESCRIPT TYPES
export type CurrentCert = {
show: boolean;
title: string;
@ -244,6 +216,7 @@ export type CertTest = {
};
export type User = {
calendar: unknown;
about: string;
acceptedPrivacyTerms: boolean;
completedChallenges: CompletedChallenge[];
@ -253,18 +226,20 @@ export type User = {
githubProfile: string;
isBanned: boolean;
isCheater: boolean;
isDonating: boolean;
isHonest: boolean;
isGithub: boolean;
isLinkedIn: boolean;
isTwitter: boolean;
isWebsite: boolean;
joinDate: string;
linkedin: string;
location: string;
name: string;
picture: string;
points: number;
portfolio: Portfolio[];
profileUI: {
isLocked: boolean;
showCerts: boolean;
showName: boolean;
};
profileUI: ProfileUI;
progressTimestamps: Array<unknown>;
sendQuincyEmail: boolean;
sound: boolean;
@ -272,9 +247,24 @@ export type User = {
twitter: string;
username: 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;
isApisMicroservicesCert: boolean;
isBackEndCert: boolean;
@ -336,8 +326,6 @@ export type FileKeyChallenge = {
tail: string;
};
// Extra types built from challengeSchema
export type ChallengeFile = {
fileKey: string;
ext: Ext;
@ -355,42 +343,3 @@ export type ChallengeFile = {
};
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 keyMap = {
NAVIGATION_MODE: 'escape',
EXECUTE_CHALLENGE: ['ctrl+enter', 'command+enter'],
FOCUS_EDITOR: 'e',
FOCUS_INSTRUCTIONS_PANEL: 'r',
NAVIGATE_PREV: ['p'],
NAVIGATE_NEXT: ['n']
navigationMode: 'escape',
executeChallenge: ['ctrl+enter', 'command+enter'],
focusEditor: 'e',
focusInstructionsPanel: 'r',
navigatePrev: ['p'],
navigateNext: ['n']
};
interface HotkeysProps {
@ -70,7 +70,7 @@ function Hotkeys({
usesMultifileEditor
}: HotkeysProps): JSX.Element {
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
// needs to be prevented.
// TODO: 'enter' on its own also disables HotKeys, but default behaviour
@ -91,22 +91,22 @@ function Hotkeys({
executeChallenge({ showCompletionModal: true });
}
},
FOCUS_EDITOR: (e: React.KeyboardEvent) => {
focusEditor: (e: React.KeyboardEvent) => {
e.preventDefault();
if (editorRef && editorRef.current) {
editorRef.current.focus();
}
},
FOCUS_INSTRUCTIONS_PANEL: () => {
focusInstructionsPanel: () => {
if (instructionsPanelRef && instructionsPanelRef.current) {
instructionsPanelRef.current.focus();
}
},
NAVIGATION_MODE: () => setEditorFocusability(false),
NAVIGATE_PREV: () => {
navigationMode: () => setEditorFocusability(false),
navigatePrev: () => {
if (!canFocusEditor) void navigate(prevChallengePath);
},
NAVIGATE_NEXT: () => {
navigateNext: () => {
if (!canFocusEditor) void navigate(nextChallengePath);
}
};

View File

@ -16,7 +16,7 @@ import {
isSignedInSelector
} from '../../../redux';
import { StepsType, User } from '../../../redux/prop-types';
import { StepsPropType, UserPropType } from '../../../redux/prop-types';
import { verifyCert } from '../../../redux/settings';
import { certMap } from '../../../resources/cert-and-project-map';
@ -31,11 +31,11 @@ const propTypes = {
errored: PropTypes.bool
}),
isSignedIn: PropTypes.bool,
steps: StepsType,
steps: StepsPropType,
superBlock: PropTypes.string,
t: PropTypes.func,
title: PropTypes.string,
user: User,
user: UserPropType,
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 { navigate } from 'gatsby';
import GreenPass from '../../../assets/icons/green-pass';
import { StepsType } from '../../../redux/prop-types';
import { StepsPropType } from '../../../redux/prop-types';
import ClaimCertSteps from './ClaimCertSteps';
const propTypes = {
@ -17,7 +17,7 @@ const propTypes = {
numberOfSteps: PropTypes.number,
completedCount: PropTypes.number
}),
steps: StepsType,
steps: StepsPropType,
superBlock: PropTypes.string
};

View File

@ -4,14 +4,14 @@ import React from 'react';
import { withTranslation, useTranslation } from 'react-i18next';
import GreenNotCompleted from '../../../assets/icons/green-not-completed';
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 propTypes = {
i18nCertText: PropTypes.string,
isProjectsCompleted: PropTypes.bool,
steps: StepsType,
steps: StepsPropType,
superBlock: PropTypes.string
};

View File

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

View File

@ -1,4 +1,4 @@
type CreateTypesType = {
type ActionTypes = {
[action: string]: string;
};
@ -13,7 +13,7 @@ type CreateTypesType = {
* @param {string} ns Name of the namespace.
* @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(
(types, action: string) => ({
...types,