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:
committed by
GitHub
parent
85359ed00a
commit
89c94e54e7
@ -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": [
|
||||
|
@ -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);
|
||||
|
@ -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)) {
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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>) {
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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;
|
@ -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,
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
@ -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
|
||||
});
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 (
|
||||
|
@ -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 } =
|
||||
|
@ -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 (
|
||||
|
@ -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
|
||||
|
@ -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...
|
||||
|
@ -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 (
|
@ -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 (
|
@ -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.
|
@ -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
|
@ -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;
|
@ -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,
|
@ -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();
|
||||
|
@ -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: () => {}
|
@ -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 },
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 */
|
||||
})
|
||||
}
|
||||
>
|
||||
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
|
@ -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> {
|
||||
|
@ -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,
|
||||
|
Reference in New Issue
Block a user