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",
|
||||||
"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": [
|
||||||
|
@ -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);
|
||||||
|
@ -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)) {
|
||||||
|
@ -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);
|
||||||
|
@ -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,
|
||||||
|
@ -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>) {
|
||||||
|
@ -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';
|
||||||
|
|
||||||
|
@ -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;
|
@ -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,
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -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
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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 (
|
||||||
|
@ -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 } =
|
||||||
|
@ -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 (
|
||||||
|
@ -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
|
||||||
|
@ -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...
|
||||||
|
@ -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 (
|
@ -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 (
|
@ -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.
|
@ -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
|
@ -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;
|
@ -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,
|
@ -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();
|
||||||
|
|
@ -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: () => {}
|
@ -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 },
|
@ -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>
|
||||||
|
@ -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
|
||||||
|
@ -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}
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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 */
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -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);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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> {
|
||||||
|
@ -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,
|
||||||
|
Reference in New Issue
Block a user