mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-09-24 23:01:18 +08:00
fix: circular dependencies (#46638)
* fix: circular dependency in formHelpers * fix: circular dependency in Flash * fix: circular dependency in helpers * fix: circular dependency in current-challenge-saga (1/2) * fix: circular dependency in code-lock-epic * feat: eslint rule for circular dependencies * fix: circular dependency in tools * fix: import error and more circular dependencies in formHelpers * fix prettier error * fix: circular dependency in danger-zone-saga * fix: circular dependency in update-email-saga * fix: circular dependency in settings-saga * fix: circular dependency in accept-terms-saga * fix: circular dependency in codeally-saga TODO: make a separate file for selectors * fix: ci lint error * fix: ci lint:prettier error * fix: circular dependency in current-challenge-saga * fix: cricular dependency in Challenges/redux * fix: circular dependency in redux/settings * fix: circular dependency in root redux * fix: lint errors * chore: remove unecessary comment * chore: remove unused export * fix: clear out actions and selectors * revert changes to package.json * test ci with maxDepth of 1 * fix: fallback to empty object * test action if it will run for maxDepth of 2
This commit is contained in:
@@ -36,6 +36,7 @@
|
||||
"import/no-named-as-default": "off",
|
||||
"import/no-named-as-default-member": "off",
|
||||
"import/order": "error",
|
||||
"import/no-cycle": [2, { "maxDepth": 2 }],
|
||||
"react/prop-types": "off",
|
||||
"no-only-tests/no-only-tests": "error"
|
||||
},
|
||||
|
||||
@@ -15,17 +15,15 @@ import { createFlashMessage } from '../components/Flash/redux';
|
||||
import { Loader, Spacer } from '../components/helpers';
|
||||
import RedirectHome from '../components/redirect-home';
|
||||
import { Themes } from '../components/settings/theme';
|
||||
import { showCert, executeGA, fetchProfileForUser } from '../redux/actions';
|
||||
import {
|
||||
showCertSelector,
|
||||
showCertFetchStateSelector,
|
||||
showCert,
|
||||
userFetchStateSelector,
|
||||
usernameSelector,
|
||||
isDonatingSelector,
|
||||
executeGA,
|
||||
userByNameSelector,
|
||||
fetchProfileForUser
|
||||
} from '../redux';
|
||||
usernameSelector
|
||||
} from '../redux/selectors';
|
||||
import { UserFetchState, User } from '../redux/prop-types';
|
||||
import { certMap } from '../resources/cert-and-project-map';
|
||||
import certificateMissingMessage from '../utils/certificate-missing-message';
|
||||
|
||||
@@ -6,12 +6,12 @@ import { isBrowser } from '../../utils/index';
|
||||
import FourOhFour from '../components/FourOhFour';
|
||||
import Loader from '../components/helpers/loader';
|
||||
import Profile from '../components/profile/profile';
|
||||
import { fetchProfileForUser } from '../redux/actions';
|
||||
import {
|
||||
usernameSelector,
|
||||
userByNameSelector,
|
||||
userProfileFetchStateSelector,
|
||||
fetchProfileForUser,
|
||||
usernameSelector
|
||||
} from '../redux';
|
||||
userProfileFetchStateSelector
|
||||
} from '../redux/selectors';
|
||||
import { User } from '../redux/prop-types';
|
||||
|
||||
interface ShowProfileOrFourOhFourProps {
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
import { SolutionDisplayWidget } from '../components/solution-display-widget';
|
||||
import ProjectPreviewModal from '../templates/Challenges/components/project-preview-modal';
|
||||
|
||||
import { openModal } from '../templates/Challenges/redux';
|
||||
import { openModal } from '../templates/Challenges/redux/actions';
|
||||
|
||||
import '../components/layouts/project-links.css';
|
||||
import { regeneratePathAndHistory } from '../../../utils/polyvinyl';
|
||||
|
||||
@@ -18,13 +18,13 @@ import Portfolio from '../components/settings/portfolio';
|
||||
import Privacy from '../components/settings/privacy';
|
||||
import { Themes } from '../components/settings/theme';
|
||||
import UserToken from '../components/settings/user-token';
|
||||
import { hardGoTo as navigate } from '../redux/actions';
|
||||
import {
|
||||
signInLoadingSelector,
|
||||
userSelector,
|
||||
isSignedInSelector,
|
||||
hardGoTo as navigate,
|
||||
userTokenSelector
|
||||
} from '../redux';
|
||||
} from '../redux/selectors';
|
||||
import { User } from '../redux/prop-types';
|
||||
import {
|
||||
submitNewAbout,
|
||||
@@ -36,7 +36,7 @@ import {
|
||||
updateMyTheme,
|
||||
updateMyKeyboardShortcuts,
|
||||
verifyCert
|
||||
} from '../redux/settings';
|
||||
} from '../redux/settings/actions';
|
||||
|
||||
const { apiLocation } = envData;
|
||||
|
||||
|
||||
@@ -16,12 +16,12 @@ import { createSelector } from 'reselect';
|
||||
import Login from '../components/Header/components/Login';
|
||||
|
||||
import { Spacer, Loader, FullWidthRow } from '../components/helpers';
|
||||
import { reportUser } from '../redux/actions';
|
||||
import {
|
||||
isSignedInSelector,
|
||||
userFetchStateSelector,
|
||||
userSelector,
|
||||
reportUser
|
||||
} from '../redux';
|
||||
isSignedInSelector,
|
||||
userSelector
|
||||
} from '../redux/selectors';
|
||||
import { UserFetchState } from '../redux/prop-types';
|
||||
|
||||
interface ShowUserProps {
|
||||
|
||||
@@ -13,19 +13,21 @@ import {
|
||||
defaultDonation,
|
||||
modalDefaultDonation
|
||||
} from '../../../../config/donation-settings';
|
||||
import { defaultDonationFormState } from '../../redux';
|
||||
import {
|
||||
addDonation,
|
||||
updateDonationFormState,
|
||||
postChargeStripe,
|
||||
postChargeStripeCard
|
||||
} from '../../redux/actions';
|
||||
import {
|
||||
isSignedInSelector,
|
||||
userSelector,
|
||||
isDonatingSelector,
|
||||
signInLoadingSelector,
|
||||
donationFormStateSelector,
|
||||
addDonation,
|
||||
updateDonationFormState,
|
||||
defaultDonationFormState,
|
||||
userSelector,
|
||||
postChargeStripe,
|
||||
postChargeStripeCard,
|
||||
isVariantASelector
|
||||
} from '../../redux';
|
||||
} from '../../redux/selectors';
|
||||
import Spacer from '../helpers/spacer';
|
||||
import { Themes } from '../settings/theme';
|
||||
import DonateCompletion from './donate-completion';
|
||||
|
||||
@@ -10,12 +10,11 @@ import { modalDefaultDonation } from '../../../../config/donation-settings';
|
||||
import Cup from '../../assets/icons/cup';
|
||||
import Heart from '../../assets/icons/heart';
|
||||
|
||||
import { closeDonationModal, executeGA } from '../../redux/actions';
|
||||
import {
|
||||
closeDonationModal,
|
||||
isDonationModalOpenSelector,
|
||||
recentlyClaimedBlockSelector,
|
||||
executeGA
|
||||
} from '../../redux';
|
||||
recentlyClaimedBlockSelector
|
||||
} from '../../redux/selectors';
|
||||
import { isLocationSuperBlock } from '../../utils/path-parsers';
|
||||
import { playTone } from '../../utils/tone';
|
||||
import { Spacer } from '../helpers';
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
defaultDonation
|
||||
} from '../../../../config/donation-settings';
|
||||
import envData from '../../../../config/env.json';
|
||||
import { signInLoadingSelector, userSelector } from '../../redux';
|
||||
import { userSelector, signInLoadingSelector } from '../../redux/selectors';
|
||||
import { Themes } from '../settings/theme';
|
||||
import PayPalButtonScriptLoader from './paypal-button-script-loader';
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
import { FlashState, State } from '../../../redux/types';
|
||||
import {
|
||||
FlashState,
|
||||
State,
|
||||
FlashApp,
|
||||
FlashMessageArg
|
||||
} from '../../../redux/types';
|
||||
import { playTone } from '../../../utils/tone';
|
||||
import { Themes } from '../../settings/theme';
|
||||
import { FlashMessages } from './flash-messages';
|
||||
|
||||
export const FlashApp = 'flash';
|
||||
|
||||
export const sagas = [];
|
||||
|
||||
export const flashMessageSelector = (state: State): FlashState['message'] =>
|
||||
@@ -19,12 +22,6 @@ enum FlashActionTypes {
|
||||
RemoveFlashMessage = 'removeFlashMessage'
|
||||
}
|
||||
|
||||
export type FlashMessageArg = {
|
||||
type: string;
|
||||
message: FlashMessages;
|
||||
variables?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
const initialState = {
|
||||
message: {
|
||||
id: '',
|
||||
|
||||
@@ -7,7 +7,7 @@ import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import envData from '../../../../../config/env.json';
|
||||
import { isSignedInSelector } from '../../../redux';
|
||||
import { isSignedInSelector } from '../../../redux/selectors';
|
||||
|
||||
const { apiLocation, homeLocation } = envData;
|
||||
|
||||
|
||||
@@ -18,8 +18,8 @@ import {
|
||||
LangCodes,
|
||||
hiddenLangs
|
||||
} from '../../../../../config/i18n/all-langs';
|
||||
import { hardGoTo as navigate } from '../../../redux';
|
||||
import { updateMyTheme } from '../../../redux/settings';
|
||||
import { hardGoTo as navigate } from '../../../redux/actions';
|
||||
import { updateMyTheme } from '../../../redux/settings/actions';
|
||||
import createLanguageRedirect from '../../create-language-redirect';
|
||||
import { Link } from '../../helpers';
|
||||
import { Themes } from '../../settings/theme';
|
||||
|
||||
@@ -9,7 +9,10 @@ import { connect } from 'react-redux';
|
||||
import Spinner from 'react-spinkit';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import { isSignedInSelector, userFetchStateSelector } from '../../../redux';
|
||||
import {
|
||||
isSignedInSelector,
|
||||
userFetchStateSelector
|
||||
} from '../../../redux/selectors';
|
||||
import Login from './Login';
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
|
||||
import { appMount } from '../redux';
|
||||
import { appMount } from '../redux/actions';
|
||||
|
||||
interface AppMountNotifierProps {
|
||||
render: () => React.ReactNode;
|
||||
|
||||
@@ -11,7 +11,6 @@ import normalizeUrl from 'normalize-url';
|
||||
import React from 'react';
|
||||
import { Field } from 'react-final-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FormOptions } from './form';
|
||||
import {
|
||||
editorValidator,
|
||||
localhostValidator,
|
||||
@@ -20,6 +19,15 @@ import {
|
||||
httpValidator
|
||||
} from './form-validators';
|
||||
|
||||
export type FormOptions = {
|
||||
ignored?: string[];
|
||||
isEditorLinkAllowed?: boolean;
|
||||
isLocalLinkAllowed?: boolean;
|
||||
required?: string[];
|
||||
types?: { [key: string]: string };
|
||||
placeholders?: { [key: string]: string };
|
||||
};
|
||||
|
||||
type FormFieldsProps = {
|
||||
formFields: { name: string; label: string }[];
|
||||
options: FormOptions;
|
||||
|
||||
@@ -1,24 +1,75 @@
|
||||
import React, { FormEvent } from 'react';
|
||||
import { Form } from 'react-final-form';
|
||||
import normalizeUrl from 'normalize-url';
|
||||
|
||||
import {
|
||||
URLValues,
|
||||
ValidatedValues,
|
||||
FormFields,
|
||||
BlockSaveButton,
|
||||
BlockSaveWrapper,
|
||||
formatUrlValues
|
||||
} from '../formHelpers/index';
|
||||
localhostValidator,
|
||||
editorValidator,
|
||||
composeValidators,
|
||||
fCCValidator,
|
||||
httpValidator
|
||||
} from './form-validators';
|
||||
import FormFields, { FormOptions } from './form-fields';
|
||||
|
||||
export type FormOptions = {
|
||||
ignored?: string[];
|
||||
isEditorLinkAllowed?: boolean;
|
||||
isLocalLinkAllowed?: boolean;
|
||||
required?: string[];
|
||||
types?: { [key: string]: string };
|
||||
placeholders?: { [key: string]: string };
|
||||
import { default as BlockSaveButton } from './block-save-button';
|
||||
import { default as BlockSaveWrapper } from './block-save-wrapper';
|
||||
|
||||
type URLValues = {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
type ValidationError = {
|
||||
error: { message?: string };
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type ValidatedValues = {
|
||||
values: URLValues;
|
||||
errors: ValidationError[];
|
||||
invalidValues: (JSX.Element | null)[];
|
||||
};
|
||||
|
||||
const normalizeOptions = {
|
||||
stripWWW: false
|
||||
};
|
||||
|
||||
function formatUrlValues(
|
||||
values: URLValues,
|
||||
options: FormOptions
|
||||
): ValidatedValues {
|
||||
const { isEditorLinkAllowed, isLocalLinkAllowed, types } = options;
|
||||
const validatedValues: ValidatedValues = {
|
||||
values: {},
|
||||
errors: [],
|
||||
invalidValues: []
|
||||
};
|
||||
const urlValues = Object.keys(values).reduce((result, key: string) => {
|
||||
let value: string = values[key];
|
||||
const nullOrWarning: JSX.Element | null = composeValidators(
|
||||
fCCValidator,
|
||||
httpValidator,
|
||||
isLocalLinkAllowed ? null : localhostValidator,
|
||||
key === 'githubLink' || isEditorLinkAllowed ? null : editorValidator
|
||||
)(value);
|
||||
if (nullOrWarning) {
|
||||
validatedValues.invalidValues.push(nullOrWarning);
|
||||
}
|
||||
if (value && types && types[key] === 'url') {
|
||||
try {
|
||||
value = normalizeUrl(value, normalizeOptions);
|
||||
} catch (err: unknown) {
|
||||
validatedValues.errors.push({
|
||||
error: err as { message?: string },
|
||||
value
|
||||
});
|
||||
}
|
||||
}
|
||||
return { ...result, [key]: value };
|
||||
}, {});
|
||||
validatedValues.values = urlValues;
|
||||
return validatedValues;
|
||||
}
|
||||
|
||||
export type FormProps = {
|
||||
buttonText?: string;
|
||||
enableSubmit?: boolean;
|
||||
|
||||
@@ -1,70 +1,5 @@
|
||||
import normalizeUrl from 'normalize-url';
|
||||
import { FormOptions } from './form';
|
||||
import {
|
||||
localhostValidator,
|
||||
editorValidator,
|
||||
composeValidators,
|
||||
fCCValidator,
|
||||
httpValidator
|
||||
} from './form-validators';
|
||||
import { default as Form, ValidatedValues } from './form';
|
||||
|
||||
export { default as BlockSaveButton } from './block-save-button';
|
||||
export { default as BlockSaveWrapper } from './block-save-wrapper';
|
||||
export { default as Form } from './form';
|
||||
export { default as FormFields } from './form-fields';
|
||||
|
||||
const normalizeOptions = {
|
||||
stripWWW: false
|
||||
};
|
||||
|
||||
export type URLValues = {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
type ValidationError = {
|
||||
error: { message?: string };
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type ValidatedValues = {
|
||||
values: URLValues;
|
||||
errors: ValidationError[];
|
||||
invalidValues: (JSX.Element | null)[];
|
||||
};
|
||||
|
||||
export function formatUrlValues(
|
||||
values: URLValues,
|
||||
options: FormOptions
|
||||
): ValidatedValues {
|
||||
const { isEditorLinkAllowed, isLocalLinkAllowed, types } = options;
|
||||
const validatedValues: ValidatedValues = {
|
||||
values: {},
|
||||
errors: [],
|
||||
invalidValues: []
|
||||
};
|
||||
const urlValues = Object.keys(values).reduce((result, key: string) => {
|
||||
let value: string = values[key];
|
||||
const nullOrWarning: JSX.Element | null = composeValidators(
|
||||
fCCValidator,
|
||||
httpValidator,
|
||||
isLocalLinkAllowed ? null : localhostValidator,
|
||||
key === 'githubLink' || isEditorLinkAllowed ? null : editorValidator
|
||||
)(value);
|
||||
if (nullOrWarning) {
|
||||
validatedValues.invalidValues.push(nullOrWarning);
|
||||
}
|
||||
if (value && types && types[key] === 'url') {
|
||||
try {
|
||||
value = normalizeUrl(value, normalizeOptions);
|
||||
} catch (err: unknown) {
|
||||
validatedValues.errors.push({
|
||||
error: err as { message?: string },
|
||||
value
|
||||
});
|
||||
}
|
||||
}
|
||||
return { ...result, [key]: value };
|
||||
}, {});
|
||||
validatedValues.values = urlValues;
|
||||
return validatedValues;
|
||||
}
|
||||
export { Form, ValidatedValues };
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import isURL from 'validator/lib/isURL';
|
||||
import { defaultUserImage } from '../../../../config/misc';
|
||||
import DefaultAvatar from '../../assets/icons/default-avatar';
|
||||
import { borderColorPicker } from '.';
|
||||
import borderColorPicker from './border-color-picker';
|
||||
|
||||
interface AvatarRendererProps {
|
||||
isDonating?: boolean;
|
||||
|
||||
@@ -7,4 +7,3 @@ export { default as Spacer } from './spacer';
|
||||
export { default as Link } from './link';
|
||||
export { default as ImageLoader } from './image-loader';
|
||||
export { default as AvatarRenderer } from './avatar-renderer';
|
||||
export { default as borderColorPicker } from './border-color-picker';
|
||||
|
||||
@@ -3,7 +3,8 @@ import Helmet from 'react-helmet';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { createSelector } from 'reselect';
|
||||
import { fetchUser, isSignedInSelector, executeGA } from '../../redux';
|
||||
import { fetchUser, executeGA } from '../../redux/actions';
|
||||
import { isSignedInSelector } from '../../redux/selectors';
|
||||
|
||||
interface CertificationProps {
|
||||
children?: React.ReactNode;
|
||||
|
||||
@@ -14,15 +14,17 @@ import robotoRegularURL from '../../../static/fonts/roboto-mono/RobotoMono-Regul
|
||||
import { isBrowser } from '../../../utils';
|
||||
import {
|
||||
fetchUser,
|
||||
isSignedInSelector,
|
||||
onlineStatusChange,
|
||||
serverStatusChange,
|
||||
executeGA
|
||||
} from '../../redux/actions';
|
||||
import {
|
||||
isSignedInSelector,
|
||||
userSelector,
|
||||
isOnlineSelector,
|
||||
isServerOnlineSelector,
|
||||
userFetchStateSelector,
|
||||
userSelector,
|
||||
executeGA
|
||||
} from '../../redux';
|
||||
userFetchStateSelector
|
||||
} from '../../redux/selectors';
|
||||
import { UserFetchState, User } from '../../redux/prop-types';
|
||||
import Flash from '../Flash';
|
||||
import { flashMessageSelector, removeFlashMessage } from '../Flash/redux';
|
||||
|
||||
@@ -3,12 +3,12 @@ import { Helmet } from 'react-helmet';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { Loader } from '../../components/helpers';
|
||||
import { tryToShowDonationModal } from '../../redux/actions';
|
||||
import {
|
||||
userSelector,
|
||||
userFetchStateSelector,
|
||||
isSignedInSelector,
|
||||
tryToShowDonationModal
|
||||
} from '../../redux';
|
||||
userSelector
|
||||
} from '../../redux/selectors';
|
||||
import DonateModal from '../Donation/donation-modal';
|
||||
import createRedirect from '../create-redirect';
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import { certificatesByNameSelector } from '../../../redux';
|
||||
import { certificatesByNameSelector } from '../../../redux/selectors';
|
||||
import type { CurrentCert } from '../../../redux/prop-types';
|
||||
import { ButtonSpacer, FullWidthRow, Link, Spacer } from '../../helpers';
|
||||
import './certifications.css';
|
||||
|
||||
@@ -17,7 +17,7 @@ import { regeneratePathAndHistory } from '../../../../../utils/polyvinyl';
|
||||
import CertificationIcon from '../../../assets/icons/certification-icon';
|
||||
import { CompletedChallenge } from '../../../redux/prop-types';
|
||||
import ProjectPreviewModal from '../../../templates/Challenges/components/project-preview-modal';
|
||||
import { openModal } from '../../../templates/Challenges/redux';
|
||||
import { openModal } from '../../../templates/Challenges/redux/actions';
|
||||
import { FullWidthRow, Link } from '../../helpers';
|
||||
import { SolutionDisplayWidget } from '../../solution-display-widget';
|
||||
import TimelinePagination from './timeline-pagination';
|
||||
|
||||
@@ -10,7 +10,7 @@ import { connect } from 'react-redux';
|
||||
|
||||
import { regeneratePathAndHistory } from '../../../../utils/polyvinyl';
|
||||
import ProjectPreviewModal from '../../templates/Challenges/components/project-preview-modal';
|
||||
import { openModal } from '../../templates/Challenges/redux';
|
||||
import { openModal } from '../../templates/Challenges/redux/actions';
|
||||
import {
|
||||
projectMap,
|
||||
legacyProjectMap
|
||||
|
||||
@@ -5,7 +5,7 @@ import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { deleteAccount, resetProgress } from '../../redux/settings';
|
||||
import { deleteAccount, resetProgress } from '../../redux/settings/actions';
|
||||
import { FullWidthRow, ButtonSpacer, Spacer } from '../helpers';
|
||||
import DeleteModal from './delete-modal';
|
||||
import ResetModal from './reset-modal';
|
||||
|
||||
@@ -14,7 +14,7 @@ import { bindActionCreators } from 'redux';
|
||||
import type { Dispatch } from 'redux';
|
||||
import isEmail from 'validator/lib/isEmail';
|
||||
|
||||
import { updateMyEmail } from '../../redux/settings';
|
||||
import { updateMyEmail } from '../../redux/settings/actions';
|
||||
import { maybeEmailRE } from '../../utils';
|
||||
|
||||
import BlockSaveButton from '../helpers/form/block-save-button';
|
||||
|
||||
@@ -6,9 +6,9 @@ import { bindActionCreators } from 'redux';
|
||||
import type { Dispatch } from 'redux';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import { userSelector } from '../../redux';
|
||||
import { userSelector } from '../../redux/selectors';
|
||||
import type { ProfileUI } from '../../redux/prop-types';
|
||||
import { submitProfileUI } from '../../redux/settings';
|
||||
import { submitProfileUI } from '../../redux/settings/actions';
|
||||
|
||||
import FullWidthRow from '../helpers/full-width-row';
|
||||
import Spacer from '../helpers/spacer';
|
||||
|
||||
@@ -4,7 +4,7 @@ import React, { Component } from 'react';
|
||||
import { TFunction, withTranslation } from 'react-i18next';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { deleteUserToken } from '../../redux';
|
||||
import { deleteUserToken } from '../../redux/actions';
|
||||
import { ButtonSpacer, FullWidthRow, Spacer } from '../helpers';
|
||||
|
||||
import './user-token.css';
|
||||
|
||||
@@ -13,11 +13,11 @@ import type { Dispatch } from 'redux';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import { isValidUsername } from '../../../../utils/validate';
|
||||
import { usernameValidationSelector } from '../../redux/settings/selectors';
|
||||
import {
|
||||
validateUsername,
|
||||
usernameValidationSelector,
|
||||
submitNewUsername
|
||||
} from '../../redux/settings';
|
||||
} from '../../redux/settings/actions';
|
||||
import BlockSaveButton from '../helpers/form/block-save-button';
|
||||
import FullWidthRow from '../helpers/full-width-row';
|
||||
|
||||
|
||||
@@ -17,7 +17,8 @@ import {
|
||||
|
||||
import { Spacer, Loader } from '../components/helpers';
|
||||
import CampersImage from '../components/landing/components/campers-image';
|
||||
import { signInLoadingSelector, userSelector, executeGA } from '../redux';
|
||||
import { executeGA } from '../redux/actions';
|
||||
import { signInLoadingSelector, userSelector } from '../redux/selectors';
|
||||
|
||||
export interface ExecuteGaArg {
|
||||
type: string;
|
||||
|
||||
@@ -11,12 +11,12 @@ import createRedirect from '../components/create-redirect';
|
||||
import { ButtonSpacer, Spacer, Loader } from '../components/helpers';
|
||||
import { apiLocation } from '../../../config/env.json';
|
||||
|
||||
import { acceptTerms } from '../redux/actions';
|
||||
import {
|
||||
acceptTerms,
|
||||
signInLoadingSelector,
|
||||
userSelector,
|
||||
isSignedInSelector,
|
||||
signInLoadingSelector
|
||||
} from '../redux';
|
||||
isSignedInSelector
|
||||
} from '../redux/selectors';
|
||||
|
||||
import './email-sign-up.css';
|
||||
interface AcceptPrivacyTermsProps {
|
||||
|
||||
@@ -11,10 +11,10 @@ import Map from '../components/Map';
|
||||
import { Spacer } from '../components/helpers';
|
||||
import LearnLayout from '../components/layouts/learn';
|
||||
import {
|
||||
userFetchStateSelector,
|
||||
isSignedInSelector,
|
||||
userSelector
|
||||
} from '../redux';
|
||||
userSelector,
|
||||
userFetchStateSelector
|
||||
} from '../redux/selectors';
|
||||
|
||||
interface FetchState {
|
||||
pending: boolean;
|
||||
|
||||
@@ -22,8 +22,8 @@ import isEmail from 'validator/lib/isEmail';
|
||||
|
||||
import { Spacer } from '../components/helpers';
|
||||
import './update-email.css';
|
||||
import { userSelector } from '../redux';
|
||||
import { updateMyEmail } from '../redux/settings';
|
||||
import { userSelector } from '../redux/selectors';
|
||||
import { updateMyEmail } from '../redux/settings/actions';
|
||||
import { maybeEmailRE } from '../utils';
|
||||
|
||||
interface UpdateEmailProps {
|
||||
|
||||
@@ -2,9 +2,8 @@ import { navigate } from 'gatsby';
|
||||
import { call, put, takeEvery } from 'redux-saga/effects';
|
||||
|
||||
import { createFlashMessage } from '../components/Flash/redux';
|
||||
|
||||
import { putUserAcceptsTerms } from '../utils/ajax';
|
||||
import { acceptTermsComplete, acceptTermsError } from './';
|
||||
import { acceptTermsComplete, acceptTermsError } from './actions';
|
||||
|
||||
function* acceptTermsSaga({ payload: quincyEmails }) {
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
import { createAction } from 'redux-actions';
|
||||
|
||||
import { actionTypes } from './action-types';
|
||||
|
||||
export const appMount = createAction(actionTypes.appMount);
|
||||
|
||||
export const tryToShowDonationModal = createAction(
|
||||
actionTypes.tryToShowDonationModal
|
||||
);
|
||||
|
||||
export const executeGA = createAction(actionTypes.executeGA);
|
||||
|
||||
export const allowBlockDonationRequests = createAction(
|
||||
actionTypes.allowBlockDonationRequests
|
||||
);
|
||||
export const closeDonationModal = createAction(actionTypes.closeDonationModal);
|
||||
export const openDonationModal = createAction(actionTypes.openDonationModal);
|
||||
export const preventBlockDonationRequests = createAction(
|
||||
actionTypes.preventBlockDonationRequests
|
||||
);
|
||||
export const preventProgressDonationRequests = createAction(
|
||||
actionTypes.preventProgressDonationRequests
|
||||
);
|
||||
export const updateDonationFormState = createAction(
|
||||
actionTypes.updateDonationFormState
|
||||
);
|
||||
|
||||
export const onlineStatusChange = createAction(actionTypes.onlineStatusChange);
|
||||
export const serverStatusChange = createAction(actionTypes.serverStatusChange);
|
||||
|
||||
// TODO: re-evaluate this since /internal is no longer used.
|
||||
// `hardGoTo` is used to hit the API server directly
|
||||
// without going through /internal
|
||||
// used for things like /signin and /signout
|
||||
export const hardGoTo = createAction(actionTypes.hardGoTo);
|
||||
|
||||
export const submitComplete = createAction(actionTypes.submitComplete);
|
||||
export const updateComplete = createAction(actionTypes.updateComplete);
|
||||
export const updateFailed = createAction(actionTypes.updateFailed);
|
||||
|
||||
export const saveChallenge = createAction(actionTypes.saveChallenge);
|
||||
export const saveChallengeComplete = createAction(
|
||||
actionTypes.saveChallengeComplete
|
||||
);
|
||||
|
||||
export const acceptTerms = createAction(actionTypes.acceptTerms);
|
||||
export const acceptTermsComplete = createAction(
|
||||
actionTypes.acceptTermsComplete
|
||||
);
|
||||
export const acceptTermsError = createAction(actionTypes.acceptTermsError);
|
||||
|
||||
export const fetchUser = createAction(actionTypes.fetchUser);
|
||||
export const fetchUserComplete = createAction(actionTypes.fetchUserComplete);
|
||||
export const fetchUserError = createAction(actionTypes.fetchUserError);
|
||||
|
||||
export const addDonation = createAction(actionTypes.addDonation);
|
||||
export const addDonationComplete = createAction(
|
||||
actionTypes.addDonationComplete
|
||||
);
|
||||
export const addDonationError = createAction(actionTypes.addDonationError);
|
||||
|
||||
export const postChargeStripe = createAction(actionTypes.postChargeStripe);
|
||||
export const postChargeStripeComplete = createAction(
|
||||
actionTypes.postChargeStripeComplete
|
||||
);
|
||||
export const postChargeStripeError = createAction(
|
||||
actionTypes.postChargeStripeError
|
||||
);
|
||||
export const postChargeStripeCard = createAction(
|
||||
actionTypes.postChargeStripeCard
|
||||
);
|
||||
export const postChargeStripeCardComplete = createAction(
|
||||
actionTypes.postChargeStripeCardComplete
|
||||
);
|
||||
export const postChargeStripeCardError = createAction(
|
||||
actionTypes.postChargeStripeCardError
|
||||
);
|
||||
|
||||
export const fetchProfileForUser = createAction(
|
||||
actionTypes.fetchProfileForUser
|
||||
);
|
||||
export const fetchProfileForUserComplete = createAction(
|
||||
actionTypes.fetchProfileForUserComplete
|
||||
);
|
||||
export const fetchProfileForUserError = createAction(
|
||||
actionTypes.fetchProfileForUserError
|
||||
);
|
||||
|
||||
export const reportUser = createAction(actionTypes.reportUser);
|
||||
export const reportUserComplete = createAction(actionTypes.reportUserComplete);
|
||||
export const reportUserError = createAction(actionTypes.reportUserError);
|
||||
|
||||
export const resetUserData = createAction(actionTypes.resetUserData);
|
||||
|
||||
export const showCert = createAction(actionTypes.showCert);
|
||||
export const showCertComplete = createAction(actionTypes.showCertComplete);
|
||||
export const showCertError = createAction(actionTypes.showCertError);
|
||||
|
||||
export const updateUserToken = createAction(actionTypes.updateUserToken);
|
||||
export const deleteUserToken = createAction(actionTypes.deleteUserToken);
|
||||
export const deleteUserTokenComplete = createAction(
|
||||
actionTypes.deleteUserTokenComplete
|
||||
);
|
||||
|
||||
export const hideCodeAlly = createAction(actionTypes.hideCodeAlly);
|
||||
export const showCodeAlly = createAction(actionTypes.showCodeAlly);
|
||||
export const tryToShowCodeAlly = createAction(actionTypes.tryToShowCodeAlly);
|
||||
|
||||
export const updateCurrentChallengeId = createAction(
|
||||
actionTypes.updateCurrentChallengeId
|
||||
);
|
||||
@@ -1,13 +1,10 @@
|
||||
import { call, put, select, takeEvery } from 'redux-saga/effects';
|
||||
|
||||
import { createFlashMessage } from '../components/Flash/redux';
|
||||
import { FlashMessages } from '../components/Flash/redux/flash-messages';
|
||||
import { postUserToken } from '../utils/ajax';
|
||||
import {
|
||||
isSignedInSelector,
|
||||
showCodeAlly,
|
||||
updateUserToken,
|
||||
userTokenSelector
|
||||
} from './';
|
||||
import { showCodeAlly, updateUserToken } from './actions';
|
||||
import { isSignedInSelector, userTokenSelector } from './selectors';
|
||||
|
||||
const startProjectErrMessage = {
|
||||
type: 'danger',
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable-next-line max-len */
|
||||
import { createStore as reduxCreateStore, applyMiddleware } from 'redux';
|
||||
import { composeWithDevTools } from 'redux-devtools-extension/logOnlyInProduction';
|
||||
import { createEpicMiddleware } from 'redux-observable';
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import i18next from 'i18next';
|
||||
import {
|
||||
call,
|
||||
delay,
|
||||
put,
|
||||
select,
|
||||
take,
|
||||
takeEvery,
|
||||
takeLeading,
|
||||
delay,
|
||||
call,
|
||||
take
|
||||
takeLeading
|
||||
} from 'redux-saga/effects';
|
||||
import i18next from 'i18next';
|
||||
|
||||
import {
|
||||
addDonation,
|
||||
@@ -15,21 +15,22 @@ import {
|
||||
postChargeStripeCard
|
||||
} from '../utils/ajax';
|
||||
import { actionTypes as appTypes } from './action-types';
|
||||
|
||||
import {
|
||||
openDonationModal,
|
||||
preventBlockDonationRequests,
|
||||
shouldRequestDonationSelector,
|
||||
preventProgressDonationRequests,
|
||||
recentlyClaimedBlockSelector,
|
||||
isDonatingSelector,
|
||||
addDonationComplete,
|
||||
addDonationError,
|
||||
openDonationModal,
|
||||
postChargeStripeCardComplete,
|
||||
postChargeStripeCardError,
|
||||
postChargeStripeComplete,
|
||||
postChargeStripeError,
|
||||
postChargeStripeCardComplete,
|
||||
postChargeStripeCardError
|
||||
} from './';
|
||||
preventBlockDonationRequests,
|
||||
preventProgressDonationRequests
|
||||
} from './actions';
|
||||
import {
|
||||
isDonatingSelector,
|
||||
recentlyClaimedBlockSelector,
|
||||
shouldRequestDonationSelector
|
||||
} from './selectors';
|
||||
|
||||
const defaultDonationErrorMessage = i18next.t('donate.error-2');
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { ofType } from 'redux-observable';
|
||||
import { merge, empty } from 'rxjs';
|
||||
import { empty, merge } from 'rxjs';
|
||||
import {
|
||||
tap,
|
||||
catchError,
|
||||
filter,
|
||||
map,
|
||||
ignoreElements,
|
||||
map,
|
||||
switchMap,
|
||||
catchError
|
||||
tap
|
||||
} from 'rxjs/operators';
|
||||
import store from 'store';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
@@ -15,11 +15,8 @@ import { backEndProject } from '../../utils/challenge-types';
|
||||
import { isGoodXHRStatus } from '../templates/Challenges/utils';
|
||||
import postUpdate$ from '../templates/Challenges/utils/post-update';
|
||||
import { actionTypes } from './action-types';
|
||||
import {
|
||||
serverStatusChange,
|
||||
isServerOnlineSelector,
|
||||
isSignedInSelector
|
||||
} from './';
|
||||
import { serverStatusChange } from './actions';
|
||||
import { isServerOnlineSelector, isSignedInSelector } from './selectors';
|
||||
|
||||
const key = 'fcc-failed-updates';
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { call, put, takeEvery } from 'redux-saga/effects';
|
||||
|
||||
import { getSessionUser, getUserProfile } from '../utils/ajax';
|
||||
import { jwt } from './cookieValues';
|
||||
import {
|
||||
fetchUserComplete,
|
||||
fetchUserError,
|
||||
fetchProfileForUserComplete,
|
||||
fetchProfileForUserError,
|
||||
fetchProfileForUserComplete
|
||||
} from './';
|
||||
fetchUserComplete,
|
||||
fetchUserError
|
||||
} from './actions';
|
||||
import { jwt } from './cookieValues';
|
||||
|
||||
function* fetchSessionUser() {
|
||||
if (!jwt) {
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
/* eslint-disable camelcase */
|
||||
import { takeEvery, call, all, select } from 'redux-saga/effects';
|
||||
import { all, call, select, takeEvery } from 'redux-saga/effects';
|
||||
|
||||
import { aBTestConfig } from '../../../config/donation-settings';
|
||||
import ga from '../analytics';
|
||||
import {
|
||||
emailSelector,
|
||||
completionCountSelector,
|
||||
completedChallengesSelector,
|
||||
recentlyClaimedBlockSelector
|
||||
} from '../redux';
|
||||
import { emailToABVariant } from '../utils/A-B-tester';
|
||||
import {
|
||||
completedChallengesSelector,
|
||||
completionCountSelector,
|
||||
emailSelector,
|
||||
recentlyClaimedBlockSelector
|
||||
} from './selectors';
|
||||
|
||||
const GaTypes = { event: ga.event, page: ga.pageview, modal: ga.modalview };
|
||||
|
||||
|
||||
+9
-334
@@ -1,29 +1,27 @@
|
||||
import { uniqBy } from 'lodash-es';
|
||||
import { createAction, handleActions } from 'redux-actions';
|
||||
import { handleActions } from 'redux-actions';
|
||||
import store from 'store';
|
||||
|
||||
import { SuperBlocks } from '../../../config/certification-settings';
|
||||
import { actionTypes as challengeTypes } from '../templates/Challenges/redux/action-types';
|
||||
import { CURRENT_CHALLENGE_KEY } from '../templates/Challenges/redux/current-challenge-saga';
|
||||
import { emailToABVariant } from '../utils/A-B-tester';
|
||||
import {
|
||||
actionTypes as challengeTypes,
|
||||
CURRENT_CHALLENGE_KEY
|
||||
} from '../templates/Challenges/redux/action-types';
|
||||
import { createAcceptTermsSaga } from './accept-terms-saga';
|
||||
import { actionTypes } from './action-types';
|
||||
import { actionTypes, ns as MainApp } from './action-types';
|
||||
import { createAppMountSaga } from './app-mount-saga';
|
||||
import { createCodeAllySaga } from './codeally-saga';
|
||||
import { createDonationSaga } from './donation-saga';
|
||||
import failedUpdatesEpic from './failed-updates-epic';
|
||||
import { createFetchUserSaga } from './fetch-user-saga';
|
||||
import { createGaSaga } from './ga-saga';
|
||||
|
||||
import hardGoToEpic from './hard-go-to-epic';
|
||||
import { createReportUserSaga } from './report-user-saga';
|
||||
import { createSaveChallengeSaga } from './save-challenge-saga';
|
||||
import { savedChallengesSelector } from './selectors';
|
||||
import { actionTypes as settingsTypes } from './settings/action-types';
|
||||
import { createShowCertSaga } from './show-cert-saga';
|
||||
import updateCompleteEpic from './update-complete-epic';
|
||||
import { createUserTokenSaga } from './user-token-saga';
|
||||
import { createSaveChallengeSaga } from './save-challenge-saga';
|
||||
|
||||
export const MainApp = 'app';
|
||||
|
||||
export const defaultFetchState = {
|
||||
pending: true,
|
||||
@@ -43,7 +41,7 @@ export const defaultDonationFormState = {
|
||||
}
|
||||
};
|
||||
|
||||
const initialState = {
|
||||
export const initialState = {
|
||||
appUsername: '',
|
||||
recentlyClaimedBlock: null,
|
||||
canRequestProgressDonation: true,
|
||||
@@ -85,329 +83,6 @@ export const sagas = [
|
||||
...createSaveChallengeSaga(actionTypes)
|
||||
];
|
||||
|
||||
export const appMount = createAction(actionTypes.appMount);
|
||||
|
||||
export const tryToShowDonationModal = createAction(
|
||||
actionTypes.tryToShowDonationModal
|
||||
);
|
||||
|
||||
export const executeGA = createAction(actionTypes.executeGA);
|
||||
|
||||
export const allowBlockDonationRequests = createAction(
|
||||
actionTypes.allowBlockDonationRequests
|
||||
);
|
||||
export const closeDonationModal = createAction(actionTypes.closeDonationModal);
|
||||
export const openDonationModal = createAction(actionTypes.openDonationModal);
|
||||
export const preventBlockDonationRequests = createAction(
|
||||
actionTypes.preventBlockDonationRequests
|
||||
);
|
||||
export const preventProgressDonationRequests = createAction(
|
||||
actionTypes.preventProgressDonationRequests
|
||||
);
|
||||
export const updateDonationFormState = createAction(
|
||||
actionTypes.updateDonationFormState
|
||||
);
|
||||
|
||||
export const onlineStatusChange = createAction(actionTypes.onlineStatusChange);
|
||||
export const serverStatusChange = createAction(actionTypes.serverStatusChange);
|
||||
|
||||
// TODO: re-evaluate this since /internal is no longer used.
|
||||
// `hardGoTo` is used to hit the API server directly
|
||||
// without going through /internal
|
||||
// used for things like /signin and /signout
|
||||
export const hardGoTo = createAction(actionTypes.hardGoTo);
|
||||
|
||||
export const submitComplete = createAction(actionTypes.submitComplete);
|
||||
export const updateComplete = createAction(actionTypes.updateComplete);
|
||||
export const updateFailed = createAction(actionTypes.updateFailed);
|
||||
|
||||
export const saveChallenge = createAction(actionTypes.saveChallenge);
|
||||
export const saveChallengeComplete = createAction(
|
||||
actionTypes.saveChallengeComplete
|
||||
);
|
||||
|
||||
export const acceptTerms = createAction(actionTypes.acceptTerms);
|
||||
export const acceptTermsComplete = createAction(
|
||||
actionTypes.acceptTermsComplete
|
||||
);
|
||||
export const acceptTermsError = createAction(actionTypes.acceptTermsError);
|
||||
|
||||
export const fetchUser = createAction(actionTypes.fetchUser);
|
||||
export const fetchUserComplete = createAction(actionTypes.fetchUserComplete);
|
||||
export const fetchUserError = createAction(actionTypes.fetchUserError);
|
||||
|
||||
export const addDonation = createAction(actionTypes.addDonation);
|
||||
export const addDonationComplete = createAction(
|
||||
actionTypes.addDonationComplete
|
||||
);
|
||||
export const addDonationError = createAction(actionTypes.addDonationError);
|
||||
|
||||
export const postChargeStripe = createAction(actionTypes.postChargeStripe);
|
||||
export const postChargeStripeComplete = createAction(
|
||||
actionTypes.postChargeStripeComplete
|
||||
);
|
||||
export const postChargeStripeError = createAction(
|
||||
actionTypes.postChargeStripeError
|
||||
);
|
||||
export const postChargeStripeCard = createAction(
|
||||
actionTypes.postChargeStripeCard
|
||||
);
|
||||
export const postChargeStripeCardComplete = createAction(
|
||||
actionTypes.postChargeStripeCardComplete
|
||||
);
|
||||
export const postChargeStripeCardError = createAction(
|
||||
actionTypes.postChargeStripeCardError
|
||||
);
|
||||
|
||||
export const fetchProfileForUser = createAction(
|
||||
actionTypes.fetchProfileForUser
|
||||
);
|
||||
export const fetchProfileForUserComplete = createAction(
|
||||
actionTypes.fetchProfileForUserComplete
|
||||
);
|
||||
export const fetchProfileForUserError = createAction(
|
||||
actionTypes.fetchProfileForUserError
|
||||
);
|
||||
|
||||
export const reportUser = createAction(actionTypes.reportUser);
|
||||
export const reportUserComplete = createAction(actionTypes.reportUserComplete);
|
||||
export const reportUserError = createAction(actionTypes.reportUserError);
|
||||
|
||||
export const resetUserData = createAction(actionTypes.resetUserData);
|
||||
|
||||
export const showCert = createAction(actionTypes.showCert);
|
||||
export const showCertComplete = createAction(actionTypes.showCertComplete);
|
||||
export const showCertError = createAction(actionTypes.showCertError);
|
||||
|
||||
export const updateUserToken = createAction(actionTypes.updateUserToken);
|
||||
export const deleteUserToken = createAction(actionTypes.deleteUserToken);
|
||||
export const deleteUserTokenComplete = createAction(
|
||||
actionTypes.deleteUserTokenComplete
|
||||
);
|
||||
|
||||
export const hideCodeAlly = createAction(actionTypes.hideCodeAlly);
|
||||
export const showCodeAlly = createAction(actionTypes.showCodeAlly);
|
||||
export const tryToShowCodeAlly = createAction(actionTypes.tryToShowCodeAlly);
|
||||
|
||||
export const updateCurrentChallengeId = createAction(
|
||||
actionTypes.updateCurrentChallengeId
|
||||
);
|
||||
|
||||
export const savedChallengesSelector = state =>
|
||||
userSelector(state).savedChallenges || [];
|
||||
export const completedChallengesSelector = state =>
|
||||
userSelector(state).completedChallenges || [];
|
||||
export const partiallyCompletedChallengesSelector = state =>
|
||||
userSelector(state).partiallyCompletedChallenges || [];
|
||||
export const completionCountSelector = state => state[MainApp].completionCount;
|
||||
export const currentChallengeIdSelector = state =>
|
||||
state[MainApp].currentChallengeId;
|
||||
|
||||
export const emailSelector = state => userSelector(state).email;
|
||||
export const isVariantASelector = state => {
|
||||
const email = emailSelector(state);
|
||||
// if the user is not signed in and the user info is not available.
|
||||
// always return A the control variant
|
||||
if (!email) return true;
|
||||
return emailToABVariant(email).isVariantA;
|
||||
};
|
||||
export const isDonatingSelector = state => userSelector(state).isDonating;
|
||||
export const isOnlineSelector = state => state[MainApp].isOnline;
|
||||
export const isServerOnlineSelector = state => state[MainApp].isServerOnline;
|
||||
export const isSignedInSelector = state => !!state[MainApp].appUsername;
|
||||
export const isDonationModalOpenSelector = state =>
|
||||
state[MainApp].showDonationModal;
|
||||
export const recentlyClaimedBlockSelector = state =>
|
||||
state[MainApp].recentlyClaimedBlock;
|
||||
export const donationFormStateSelector = state =>
|
||||
state[MainApp].donationFormState;
|
||||
export const signInLoadingSelector = state =>
|
||||
userFetchStateSelector(state).pending;
|
||||
export const showCertSelector = state => state[MainApp].showCert;
|
||||
export const showCertFetchStateSelector = state =>
|
||||
state[MainApp].showCertFetchState;
|
||||
export const shouldRequestDonationSelector = state => {
|
||||
const completedChallenges = completedChallengesSelector(state);
|
||||
const completionCount = completionCountSelector(state);
|
||||
const canRequestProgressDonation = state[MainApp].canRequestProgressDonation;
|
||||
const isDonating = isDonatingSelector(state);
|
||||
const recentlyClaimedBlock = recentlyClaimedBlockSelector(state);
|
||||
|
||||
// don't request donation if already donating
|
||||
if (isDonating) return false;
|
||||
|
||||
// a block has been completed
|
||||
if (recentlyClaimedBlock) return true;
|
||||
|
||||
// a donation has already been requested
|
||||
if (!canRequestProgressDonation) return false;
|
||||
|
||||
// donations only appear after the user has completed ten challenges (i.e.
|
||||
// not before the 11th challenge has mounted)
|
||||
if (completedChallenges.length < 10) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// this will mean we have completed 3 or more challenges this browser session
|
||||
// and enough challenges overall to not be new
|
||||
return completionCount >= 3;
|
||||
};
|
||||
|
||||
export const userTokenSelector = state => {
|
||||
return userSelector(state).userToken;
|
||||
};
|
||||
|
||||
export const showCodeAllySelector = state => {
|
||||
return state[MainApp].showCodeAlly;
|
||||
};
|
||||
|
||||
export const userByNameSelector = username => state => {
|
||||
const { user } = state[MainApp];
|
||||
// return initial state empty user empty object instead of empty
|
||||
// object litteral to prevent components from re-rendering unnecessarily
|
||||
return user[username] ?? initialState.user;
|
||||
};
|
||||
|
||||
export const currentCertsSelector = state =>
|
||||
certificatesByNameSelector(state[MainApp]?.appUsername)(state)?.currentCerts;
|
||||
|
||||
export const certificatesByNameSelector = username => state => {
|
||||
const {
|
||||
isRespWebDesignCert,
|
||||
is2018DataVisCert,
|
||||
isFrontEndLibsCert,
|
||||
isJsAlgoDataStructCert,
|
||||
isApisMicroservicesCert,
|
||||
isInfosecQaCert,
|
||||
isQaCertV7,
|
||||
isInfosecCertV7,
|
||||
isFrontEndCert,
|
||||
isBackEndCert,
|
||||
isDataVisCert,
|
||||
isFullStackCert,
|
||||
isSciCompPyCertV7,
|
||||
isDataAnalysisPyCertV7,
|
||||
isMachineLearningPyCertV7,
|
||||
isRelationalDatabaseCertV8
|
||||
} = userByNameSelector(username)(state);
|
||||
return {
|
||||
hasModernCert:
|
||||
isRespWebDesignCert ||
|
||||
is2018DataVisCert ||
|
||||
isFrontEndLibsCert ||
|
||||
isJsAlgoDataStructCert ||
|
||||
isApisMicroservicesCert ||
|
||||
isQaCertV7 ||
|
||||
isInfosecCertV7 ||
|
||||
isFullStackCert ||
|
||||
isSciCompPyCertV7 ||
|
||||
isDataAnalysisPyCertV7 ||
|
||||
isMachineLearningPyCertV7 ||
|
||||
isRelationalDatabaseCertV8,
|
||||
hasLegacyCert:
|
||||
isFrontEndCert || isBackEndCert || isDataVisCert || isInfosecQaCert,
|
||||
isFullStackCert,
|
||||
currentCerts: [
|
||||
{
|
||||
show: isRespWebDesignCert,
|
||||
title: 'Responsive Web Design Certification',
|
||||
certSlug: SuperBlocks.RespWebDesign
|
||||
},
|
||||
{
|
||||
show: isJsAlgoDataStructCert,
|
||||
title: 'JavaScript Algorithms and Data Structures Certification',
|
||||
certSlug: SuperBlocks.JsAlgoDataStruct
|
||||
},
|
||||
{
|
||||
show: isFrontEndLibsCert,
|
||||
title: 'Front End Development Libraries Certification',
|
||||
certSlug: SuperBlocks.FrontEndDevLibs
|
||||
},
|
||||
{
|
||||
show: is2018DataVisCert,
|
||||
title: 'Data Visualization Certification',
|
||||
certSlug: SuperBlocks.DataVis
|
||||
},
|
||||
{
|
||||
show: isApisMicroservicesCert,
|
||||
title: 'Back End Development and APIs Certification',
|
||||
certSlug: SuperBlocks.BackEndDevApis
|
||||
},
|
||||
{
|
||||
show: isQaCertV7,
|
||||
title: ' Quality Assurance Certification',
|
||||
certSlug: 'quality-assurance-v7'
|
||||
},
|
||||
{
|
||||
show: isInfosecCertV7,
|
||||
title: 'Information Security Certification',
|
||||
certSlug: 'information-security-v7'
|
||||
},
|
||||
{
|
||||
show: isSciCompPyCertV7,
|
||||
title: 'Scientific Computing with Python Certification',
|
||||
certSlug: 'scientific-computing-with-python-v7'
|
||||
},
|
||||
{
|
||||
show: isDataAnalysisPyCertV7,
|
||||
title: 'Data Analysis with Python Certification',
|
||||
certSlug: 'data-analysis-with-python-v7'
|
||||
},
|
||||
{
|
||||
show: isMachineLearningPyCertV7,
|
||||
title: 'Machine Learning with Python Certification',
|
||||
certSlug: 'machine-learning-with-python-v7'
|
||||
},
|
||||
{
|
||||
show: isRelationalDatabaseCertV8,
|
||||
title: 'Relational Database Certification',
|
||||
certSlug: 'relational-database-v8'
|
||||
}
|
||||
],
|
||||
legacyCerts: [
|
||||
{
|
||||
show: isFrontEndCert,
|
||||
title: 'Front End Certification',
|
||||
certSlug: 'legacy-front-end'
|
||||
},
|
||||
{
|
||||
show: isBackEndCert,
|
||||
title: 'Back End Certification',
|
||||
certSlug: 'legacy-back-end'
|
||||
},
|
||||
{
|
||||
show: isDataVisCert,
|
||||
title: 'Data Visualization Certification',
|
||||
certSlug: 'legacy-data-visualization'
|
||||
},
|
||||
{
|
||||
show: isInfosecQaCert,
|
||||
title: 'Information Security and Quality Assurance Certification',
|
||||
// Keep the current public profile cert slug
|
||||
certSlug: 'information-security-and-quality-assurance'
|
||||
},
|
||||
{
|
||||
show: isFullStackCert,
|
||||
title: 'Full Stack Certification',
|
||||
// Keep the current public profile cert slug
|
||||
certSlug: 'full-stack'
|
||||
}
|
||||
]
|
||||
};
|
||||
};
|
||||
|
||||
export const userFetchStateSelector = state => state[MainApp].userFetchState;
|
||||
export const userProfileFetchStateSelector = state =>
|
||||
state[MainApp].userProfileFetchState;
|
||||
export const usernameSelector = state => state[MainApp].appUsername;
|
||||
export const userSelector = state => {
|
||||
const username = usernameSelector(state);
|
||||
|
||||
return state[MainApp].user[username] || {};
|
||||
};
|
||||
|
||||
export const sessionMetaSelector = state => state[MainApp].sessionMeta;
|
||||
|
||||
function spreadThePayloadOnUser(state, payload) {
|
||||
return {
|
||||
...state,
|
||||
|
||||
@@ -2,9 +2,8 @@ import { navigate } from 'gatsby';
|
||||
import { call, put, takeEvery } from 'redux-saga/effects';
|
||||
|
||||
import { createFlashMessage } from '../components/Flash/redux';
|
||||
|
||||
import { postReportUser } from '../utils/ajax';
|
||||
import { reportUserComplete, reportUserError } from './';
|
||||
import { reportUserComplete, reportUserError } from './actions';
|
||||
|
||||
function* reportUserSaga({ payload }) {
|
||||
try {
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
import { combineReducers } from 'redux';
|
||||
|
||||
import { reducer as flash } from '../components/Flash/redux';
|
||||
import {
|
||||
reducer as flash,
|
||||
FlashApp as flashNameSpace
|
||||
} from '../components/Flash/redux';
|
||||
import {
|
||||
reducer as search,
|
||||
ns as searchNameSpace
|
||||
ns as searchNameSpace,
|
||||
reducer as search
|
||||
} from '../components/search/redux';
|
||||
import {
|
||||
reducer as challenge,
|
||||
ns as challengeNameSpace
|
||||
ns as challengeNameSpace,
|
||||
reducer as challenge
|
||||
} from '../templates/Challenges/redux';
|
||||
import {
|
||||
reducer as curriculumMap,
|
||||
ns as curriculumMapNameSpace
|
||||
ns as curriculumMapNameSpace,
|
||||
reducer as curriculumMap
|
||||
} from '../templates/Introduction/redux';
|
||||
import { reducer as settings, ns as settingsNameSpace } from './settings';
|
||||
import { reducer as app, MainApp as appNameSpace } from './';
|
||||
import { ns as appNameSpace } from './action-types';
|
||||
import { ns as settingsNameSpace, reducer as settings } from './settings';
|
||||
import { FlashApp as flashNameSpace } from './types';
|
||||
import { reducer as app } from './';
|
||||
|
||||
export default combineReducers({
|
||||
[appNameSpace]: app,
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
import { call, takeEvery, put, select } from 'redux-saga/effects';
|
||||
import { postSaveChallenge, mapFilesToChallengeFiles } from '../utils/ajax';
|
||||
import { call, put, select, takeEvery } from 'redux-saga/effects';
|
||||
|
||||
import { challengeTypes } from '../../utils/challenge-types';
|
||||
import { createFlashMessage } from '../components/Flash/redux';
|
||||
import { FlashMessages } from '../components/Flash/redux/flash-messages';
|
||||
import {
|
||||
challengeDataSelector,
|
||||
challengeMetaSelector
|
||||
} from '../templates/Challenges/redux';
|
||||
import { createFlashMessage } from '../components/Flash/redux';
|
||||
import { challengeTypes } from '../../utils/challenge-types';
|
||||
import { FlashMessages } from '../components/Flash/redux/flash-messages';
|
||||
} from '../templates/Challenges/redux/selectors';
|
||||
import { mapFilesToChallengeFiles, postSaveChallenge } from '../utils/ajax';
|
||||
import {
|
||||
standardizeRequestBody,
|
||||
getStringSizeInBytes,
|
||||
bodySizeFits,
|
||||
MAX_BODY_SIZE
|
||||
getStringSizeInBytes,
|
||||
MAX_BODY_SIZE,
|
||||
standardizeRequestBody
|
||||
} from '../utils/challenge-request-helpers';
|
||||
import { saveChallengeComplete, savedChallengesSelector } from './';
|
||||
import { saveChallengeComplete } from './actions';
|
||||
import { savedChallengesSelector } from './selectors';
|
||||
|
||||
export function* saveChallengeSaga() {
|
||||
const { id, challengeType } = yield select(challengeMetaSelector);
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
import { SuperBlocks } from '../../../config/certification-settings';
|
||||
import { emailToABVariant } from '../utils/A-B-tester';
|
||||
import { ns as MainApp } from './action-types';
|
||||
|
||||
export const savedChallengesSelector = state =>
|
||||
userSelector(state).savedChallenges || [];
|
||||
export const completedChallengesSelector = state =>
|
||||
userSelector(state).completedChallenges || [];
|
||||
export const partiallyCompletedChallengesSelector = state =>
|
||||
userSelector(state).partiallyCompletedChallenges || [];
|
||||
export const completionCountSelector = state => state[MainApp].completionCount;
|
||||
export const currentChallengeIdSelector = state =>
|
||||
state[MainApp].currentChallengeId;
|
||||
|
||||
export const emailSelector = state => userSelector(state).email;
|
||||
export const isVariantASelector = state => {
|
||||
const email = emailSelector(state);
|
||||
// if the user is not signed in and the user info is not available.
|
||||
// always return A the control variant
|
||||
if (!email) return true;
|
||||
return emailToABVariant(email).isVariantA;
|
||||
};
|
||||
export const isDonatingSelector = state => userSelector(state).isDonating;
|
||||
export const isOnlineSelector = state => state[MainApp].isOnline;
|
||||
export const isServerOnlineSelector = state => state[MainApp].isServerOnline;
|
||||
export const isSignedInSelector = state => !!state[MainApp].appUsername;
|
||||
export const isDonationModalOpenSelector = state =>
|
||||
state[MainApp].showDonationModal;
|
||||
export const recentlyClaimedBlockSelector = state =>
|
||||
state[MainApp].recentlyClaimedBlock;
|
||||
export const donationFormStateSelector = state =>
|
||||
state[MainApp].donationFormState;
|
||||
export const signInLoadingSelector = state =>
|
||||
userFetchStateSelector(state).pending;
|
||||
export const showCertSelector = state => state[MainApp].showCert;
|
||||
export const showCertFetchStateSelector = state =>
|
||||
state[MainApp].showCertFetchState;
|
||||
export const shouldRequestDonationSelector = state => {
|
||||
const completedChallenges = completedChallengesSelector(state);
|
||||
const completionCount = completionCountSelector(state);
|
||||
const canRequestProgressDonation = state[MainApp].canRequestProgressDonation;
|
||||
const isDonating = isDonatingSelector(state);
|
||||
const recentlyClaimedBlock = recentlyClaimedBlockSelector(state);
|
||||
|
||||
// don't request donation if already donating
|
||||
if (isDonating) return false;
|
||||
|
||||
// a block has been completed
|
||||
if (recentlyClaimedBlock) return true;
|
||||
|
||||
// a donation has already been requested
|
||||
if (!canRequestProgressDonation) return false;
|
||||
|
||||
// donations only appear after the user has completed ten challenges (i.e.
|
||||
// not before the 11th challenge has mounted)
|
||||
if (completedChallenges.length < 10) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// this will mean we have completed 3 or more challenges this browser session
|
||||
// and enough challenges overall to not be new
|
||||
return completionCount >= 3;
|
||||
};
|
||||
|
||||
export const userTokenSelector = state => {
|
||||
return userSelector(state).userToken;
|
||||
};
|
||||
|
||||
export const showCodeAllySelector = state => {
|
||||
return state[MainApp].showCodeAlly;
|
||||
};
|
||||
|
||||
export const userByNameSelector = username => state => {
|
||||
const { user } = state[MainApp];
|
||||
// return initial state empty user empty object instead of empty
|
||||
// object litteral to prevent components from re-rendering unnecessarily
|
||||
// TODO: confirm if "initialState" can be moved here or action-types.js
|
||||
return user[username] ?? {};
|
||||
};
|
||||
|
||||
export const currentCertsSelector = state =>
|
||||
certificatesByNameSelector(state[MainApp]?.appUsername)(state)?.currentCerts;
|
||||
|
||||
export const certificatesByNameSelector = username => state => {
|
||||
const {
|
||||
isRespWebDesignCert,
|
||||
is2018DataVisCert,
|
||||
isFrontEndLibsCert,
|
||||
isJsAlgoDataStructCert,
|
||||
isApisMicroservicesCert,
|
||||
isInfosecQaCert,
|
||||
isQaCertV7,
|
||||
isInfosecCertV7,
|
||||
isFrontEndCert,
|
||||
isBackEndCert,
|
||||
isDataVisCert,
|
||||
isFullStackCert,
|
||||
isSciCompPyCertV7,
|
||||
isDataAnalysisPyCertV7,
|
||||
isMachineLearningPyCertV7,
|
||||
isRelationalDatabaseCertV8
|
||||
} = userByNameSelector(username)(state);
|
||||
return {
|
||||
hasModernCert:
|
||||
isRespWebDesignCert ||
|
||||
is2018DataVisCert ||
|
||||
isFrontEndLibsCert ||
|
||||
isJsAlgoDataStructCert ||
|
||||
isApisMicroservicesCert ||
|
||||
isQaCertV7 ||
|
||||
isInfosecCertV7 ||
|
||||
isFullStackCert ||
|
||||
isSciCompPyCertV7 ||
|
||||
isDataAnalysisPyCertV7 ||
|
||||
isMachineLearningPyCertV7 ||
|
||||
isRelationalDatabaseCertV8,
|
||||
hasLegacyCert:
|
||||
isFrontEndCert || isBackEndCert || isDataVisCert || isInfosecQaCert,
|
||||
isFullStackCert,
|
||||
currentCerts: [
|
||||
{
|
||||
show: isRespWebDesignCert,
|
||||
title: 'Responsive Web Design Certification',
|
||||
certSlug: SuperBlocks.RespWebDesign
|
||||
},
|
||||
{
|
||||
show: isJsAlgoDataStructCert,
|
||||
title: 'JavaScript Algorithms and Data Structures Certification',
|
||||
certSlug: SuperBlocks.JsAlgoDataStruct
|
||||
},
|
||||
{
|
||||
show: isFrontEndLibsCert,
|
||||
title: 'Front End Development Libraries Certification',
|
||||
certSlug: SuperBlocks.FrontEndDevLibs
|
||||
},
|
||||
{
|
||||
show: is2018DataVisCert,
|
||||
title: 'Data Visualization Certification',
|
||||
certSlug: SuperBlocks.DataVis
|
||||
},
|
||||
{
|
||||
show: isApisMicroservicesCert,
|
||||
title: 'Back End Development and APIs Certification',
|
||||
certSlug: SuperBlocks.BackEndDevApis
|
||||
},
|
||||
{
|
||||
show: isQaCertV7,
|
||||
title: ' Quality Assurance Certification',
|
||||
certSlug: 'quality-assurance-v7'
|
||||
},
|
||||
{
|
||||
show: isInfosecCertV7,
|
||||
title: 'Information Security Certification',
|
||||
certSlug: 'information-security-v7'
|
||||
},
|
||||
{
|
||||
show: isSciCompPyCertV7,
|
||||
title: 'Scientific Computing with Python Certification',
|
||||
certSlug: 'scientific-computing-with-python-v7'
|
||||
},
|
||||
{
|
||||
show: isDataAnalysisPyCertV7,
|
||||
title: 'Data Analysis with Python Certification',
|
||||
certSlug: 'data-analysis-with-python-v7'
|
||||
},
|
||||
{
|
||||
show: isMachineLearningPyCertV7,
|
||||
title: 'Machine Learning with Python Certification',
|
||||
certSlug: 'machine-learning-with-python-v7'
|
||||
},
|
||||
{
|
||||
show: isRelationalDatabaseCertV8,
|
||||
title: 'Relational Database Certification',
|
||||
certSlug: 'relational-database-v8'
|
||||
}
|
||||
],
|
||||
legacyCerts: [
|
||||
{
|
||||
show: isFrontEndCert,
|
||||
title: 'Front End Certification',
|
||||
certSlug: 'legacy-front-end'
|
||||
},
|
||||
{
|
||||
show: isBackEndCert,
|
||||
title: 'Back End Certification',
|
||||
certSlug: 'legacy-back-end'
|
||||
},
|
||||
{
|
||||
show: isDataVisCert,
|
||||
title: 'Data Visualization Certification',
|
||||
certSlug: 'legacy-data-visualization'
|
||||
},
|
||||
{
|
||||
show: isInfosecQaCert,
|
||||
title: 'Information Security and Quality Assurance Certification',
|
||||
// Keep the current public profile cert slug
|
||||
certSlug: 'information-security-and-quality-assurance'
|
||||
},
|
||||
{
|
||||
show: isFullStackCert,
|
||||
title: 'Full Stack Certification',
|
||||
// Keep the current public profile cert slug
|
||||
certSlug: 'full-stack'
|
||||
}
|
||||
]
|
||||
};
|
||||
};
|
||||
|
||||
export const userFetchStateSelector = state => state[MainApp].userFetchState;
|
||||
export const userProfileFetchStateSelector = state =>
|
||||
state[MainApp].userProfileFetchState;
|
||||
export const usernameSelector = state => state[MainApp].appUsername;
|
||||
export const userSelector = state => {
|
||||
const username = usernameSelector(state);
|
||||
|
||||
return state[MainApp].user[username] || {};
|
||||
};
|
||||
|
||||
export const sessionMetaSelector = state => state[MainApp].sessionMeta;
|
||||
@@ -0,0 +1,109 @@
|
||||
import { createAction } from 'redux-actions';
|
||||
|
||||
import { actionTypes as types } from './action-types';
|
||||
|
||||
const checkForSuccessPayload = ({ type, payload }) =>
|
||||
type === 'success' ? payload : null;
|
||||
|
||||
export const submitNewAbout = createAction(types.submitNewAbout);
|
||||
export const submitNewAboutComplete = createAction(
|
||||
types.submitNewAboutComplete,
|
||||
checkForSuccessPayload
|
||||
);
|
||||
export const submitNewAboutError = createAction(types.submitNewAboutError);
|
||||
|
||||
export const submitNewUsername = createAction(types.submitNewUsername);
|
||||
export const submitNewUsernameComplete = createAction(
|
||||
types.submitNewUsernameComplete,
|
||||
({ type, username }) => (type === 'success' ? username : null)
|
||||
);
|
||||
export const submitNewUsernameError = createAction(
|
||||
types.submitNewUsernameError
|
||||
);
|
||||
|
||||
export const submitProfileUI = createAction(types.submitProfileUI);
|
||||
export const submitProfileUIComplete = createAction(
|
||||
types.submitProfileUIComplete,
|
||||
checkForSuccessPayload
|
||||
);
|
||||
export const submitProfileUIError = createAction(types.submitProfileUIError);
|
||||
|
||||
export const updateMyEmail = createAction(types.updateMyEmail);
|
||||
export const updateMyEmailComplete = createAction(types.updateMyEmailComplete);
|
||||
export const updateMyEmailError = createAction(types.updateMyEmailError);
|
||||
|
||||
export const updateMySocials = createAction(types.updateMySocials);
|
||||
export const updateMySocialsComplete = createAction(
|
||||
types.updateMySocialsComplete,
|
||||
checkForSuccessPayload
|
||||
);
|
||||
export const updateMySocialsError = createAction(types.updateMySocialsError);
|
||||
|
||||
export const updateMySound = createAction(types.updateMySound);
|
||||
export const updateMySoundComplete = createAction(
|
||||
types.updateMySoundComplete,
|
||||
checkForSuccessPayload
|
||||
);
|
||||
export const updateMySoundError = createAction(types.updateMySoundError);
|
||||
|
||||
export const updateMyTheme = createAction(types.updateMyTheme);
|
||||
export const updateMyThemeComplete = createAction(
|
||||
types.updateMyThemeComplete,
|
||||
checkForSuccessPayload
|
||||
);
|
||||
export const updateMyThemeError = createAction(types.updateMyThemeError);
|
||||
|
||||
export const updateMyKeyboardShortcuts = createAction(
|
||||
types.updateMyKeyboardShortcuts
|
||||
);
|
||||
export const updateMyKeyboardShortcutsComplete = createAction(
|
||||
types.updateMyKeyboardShortcutsComplete,
|
||||
checkForSuccessPayload
|
||||
);
|
||||
export const updateMyKeyboardShortcutsError = createAction(
|
||||
types.updateMyKeyboardShortcutsError
|
||||
);
|
||||
|
||||
export const updateMyHonesty = createAction(types.updateMyHonesty);
|
||||
export const updateMyHonestyComplete = createAction(
|
||||
types.updateMyHonestyComplete,
|
||||
checkForSuccessPayload
|
||||
);
|
||||
export const updateMyHonestyError = createAction(types.updateMyHonestyError);
|
||||
|
||||
export const updateMyQuincyEmail = createAction(types.updateMyQuincyEmail);
|
||||
export const updateMyQuincyEmailComplete = createAction(
|
||||
types.updateMyQuincyEmailComplete,
|
||||
checkForSuccessPayload
|
||||
);
|
||||
export const updateMyQuincyEmailError = createAction(
|
||||
types.updateMyQuincyEmailError
|
||||
);
|
||||
|
||||
export const updateMyPortfolio = createAction(types.updateMyPortfolio);
|
||||
export const updateMyPortfolioComplete = createAction(
|
||||
types.updateMyPortfolioComplete,
|
||||
checkForSuccessPayload
|
||||
);
|
||||
export const updateMyPortfolioError = createAction(
|
||||
types.updateMyPortfolioError
|
||||
);
|
||||
|
||||
export const validateUsername = createAction(types.validateUsername);
|
||||
export const validateUsernameComplete = createAction(
|
||||
types.validateUsernameComplete
|
||||
);
|
||||
export const validateUsernameError = createAction(types.validateUsernameError);
|
||||
|
||||
export const verifyCert = createAction(types.verifyCert);
|
||||
export const verifyCertComplete = createAction(
|
||||
types.verifyCertComplete,
|
||||
checkForSuccessPayload
|
||||
);
|
||||
export const verifyCertError = createAction(types.verifyCertError);
|
||||
|
||||
export const resetProgress = createAction(types.resetProgress);
|
||||
export const resetProgressError = createAction(types.resetProgressError);
|
||||
|
||||
export const deleteAccount = createAction(types.deleteAccount);
|
||||
export const deleteAccountError = createAction(types.deleteAccountError);
|
||||
@@ -1,12 +1,12 @@
|
||||
import { navigate } from 'gatsby';
|
||||
import { call, put, takeEvery, take } from 'redux-saga/effects';
|
||||
import { call, put, take, takeEvery } from 'redux-saga/effects';
|
||||
|
||||
import { resetUserData, fetchUser } from '../';
|
||||
import { createFlashMessage } from '../../components/Flash/redux';
|
||||
import { FlashMessages } from '../../components/Flash/redux/flash-messages';
|
||||
import { postResetProgress, postDeleteAccount } from '../../utils/ajax';
|
||||
import { postDeleteAccount, postResetProgress } from '../../utils/ajax';
|
||||
import { actionTypes as appTypes } from '../action-types';
|
||||
import { deleteAccountError, resetProgressError } from './';
|
||||
import { fetchUser, resetUserData } from '../actions';
|
||||
import { deleteAccountError, resetProgressError } from './actions';
|
||||
|
||||
function* deleteAccountSaga() {
|
||||
try {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createAction, handleActions } from 'redux-actions';
|
||||
import { handleActions } from 'redux-actions';
|
||||
import { actionTypes as types, ns } from './action-types';
|
||||
import { createDangerZoneSaga } from './danger-zone-saga';
|
||||
import { createSettingsSagas } from './settings-sagas';
|
||||
@@ -26,114 +26,6 @@ export const sagas = [
|
||||
...createDangerZoneSaga(types)
|
||||
];
|
||||
|
||||
const checkForSuccessPayload = ({ type, payload }) =>
|
||||
type === 'success' ? payload : null;
|
||||
|
||||
export const submitNewAbout = createAction(types.submitNewAbout);
|
||||
export const submitNewAboutComplete = createAction(
|
||||
types.submitNewAboutComplete,
|
||||
checkForSuccessPayload
|
||||
);
|
||||
export const submitNewAboutError = createAction(types.submitNewAboutError);
|
||||
|
||||
export const submitNewUsername = createAction(types.submitNewUsername);
|
||||
export const submitNewUsernameComplete = createAction(
|
||||
types.submitNewUsernameComplete,
|
||||
({ type, username }) => (type === 'success' ? username : null)
|
||||
);
|
||||
export const submitNewUsernameError = createAction(
|
||||
types.submitNewUsernameError
|
||||
);
|
||||
|
||||
export const submitProfileUI = createAction(types.submitProfileUI);
|
||||
export const submitProfileUIComplete = createAction(
|
||||
types.submitProfileUIComplete,
|
||||
checkForSuccessPayload
|
||||
);
|
||||
export const submitProfileUIError = createAction(types.submitProfileUIError);
|
||||
|
||||
export const updateMyEmail = createAction(types.updateMyEmail);
|
||||
export const updateMyEmailComplete = createAction(types.updateMyEmailComplete);
|
||||
export const updateMyEmailError = createAction(types.updateMyEmailError);
|
||||
|
||||
export const updateMySocials = createAction(types.updateMySocials);
|
||||
export const updateMySocialsComplete = createAction(
|
||||
types.updateMySocialsComplete,
|
||||
checkForSuccessPayload
|
||||
);
|
||||
export const updateMySocialsError = createAction(types.updateMySocialsError);
|
||||
|
||||
export const updateMySound = createAction(types.updateMySound);
|
||||
export const updateMySoundComplete = createAction(
|
||||
types.updateMySoundComplete,
|
||||
checkForSuccessPayload
|
||||
);
|
||||
export const updateMySoundError = createAction(types.updateMySoundError);
|
||||
|
||||
export const updateMyTheme = createAction(types.updateMyTheme);
|
||||
export const updateMyThemeComplete = createAction(
|
||||
types.updateMyThemeComplete,
|
||||
checkForSuccessPayload
|
||||
);
|
||||
export const updateMyThemeError = createAction(types.updateMyThemeError);
|
||||
|
||||
export const updateMyKeyboardShortcuts = createAction(
|
||||
types.updateMyKeyboardShortcuts
|
||||
);
|
||||
export const updateMyKeyboardShortcutsComplete = createAction(
|
||||
types.updateMyKeyboardShortcutsComplete,
|
||||
checkForSuccessPayload
|
||||
);
|
||||
export const updateMyKeyboardShortcutsError = createAction(
|
||||
types.updateMyKeyboardShortcutsError
|
||||
);
|
||||
|
||||
export const updateMyHonesty = createAction(types.updateMyHonesty);
|
||||
export const updateMyHonestyComplete = createAction(
|
||||
types.updateMyHonestyComplete,
|
||||
checkForSuccessPayload
|
||||
);
|
||||
export const updateMyHonestyError = createAction(types.updateMyHonestyError);
|
||||
|
||||
export const updateMyQuincyEmail = createAction(types.updateMyQuincyEmail);
|
||||
export const updateMyQuincyEmailComplete = createAction(
|
||||
types.updateMyQuincyEmailComplete,
|
||||
checkForSuccessPayload
|
||||
);
|
||||
export const updateMyQuincyEmailError = createAction(
|
||||
types.updateMyQuincyEmailError
|
||||
);
|
||||
|
||||
export const updateMyPortfolio = createAction(types.updateMyPortfolio);
|
||||
export const updateMyPortfolioComplete = createAction(
|
||||
types.updateMyPortfolioComplete,
|
||||
checkForSuccessPayload
|
||||
);
|
||||
export const updateMyPortfolioError = createAction(
|
||||
types.updateMyPortfolioError
|
||||
);
|
||||
|
||||
export const validateUsername = createAction(types.validateUsername);
|
||||
export const validateUsernameComplete = createAction(
|
||||
types.validateUsernameComplete
|
||||
);
|
||||
export const validateUsernameError = createAction(types.validateUsernameError);
|
||||
|
||||
export const verifyCert = createAction(types.verifyCert);
|
||||
export const verifyCertComplete = createAction(
|
||||
types.verifyCertComplete,
|
||||
checkForSuccessPayload
|
||||
);
|
||||
export const verifyCertError = createAction(types.verifyCertError);
|
||||
|
||||
export const resetProgress = createAction(types.resetProgress);
|
||||
export const resetProgressError = createAction(types.resetProgressError);
|
||||
|
||||
export const deleteAccount = createAction(types.deleteAccount);
|
||||
export const deleteAccountError = createAction(types.deleteAccountError);
|
||||
|
||||
export const usernameValidationSelector = state => state[ns].usernameValidation;
|
||||
|
||||
export const reducer = handleActions(
|
||||
{
|
||||
[types.submitNewUsernameComplete]: state => ({
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import { ns } from './action-types';
|
||||
|
||||
export const usernameValidationSelector = state => state[ns].usernameValidation;
|
||||
@@ -1,61 +1,61 @@
|
||||
import { omit } from 'lodash-es';
|
||||
import {
|
||||
call,
|
||||
debounce,
|
||||
put,
|
||||
select,
|
||||
takeLatest,
|
||||
takeEvery,
|
||||
debounce
|
||||
takeLatest
|
||||
} from 'redux-saga/effects';
|
||||
import store from 'store';
|
||||
|
||||
import {
|
||||
certTypeIdMap,
|
||||
certTypes
|
||||
} from '../../../../config/certification-settings';
|
||||
import { createFlashMessage } from '../../components/Flash/redux';
|
||||
import { certMap } from '../../resources/cert-and-project-map';
|
||||
import {
|
||||
getUsernameExists,
|
||||
putUpdateMyAbout,
|
||||
putUpdateMyProfileUI,
|
||||
putUpdateMyUsername,
|
||||
putUpdateMySocials,
|
||||
putUpdateMyHonesty,
|
||||
putUpdateMyQuincyEmail,
|
||||
putVerifyCert,
|
||||
putUpdateMyKeyboardShortcuts,
|
||||
putUpdateMyPortfolio,
|
||||
putUpdateMyTheme,
|
||||
putUpdateMyProfileUI,
|
||||
putUpdateMyQuincyEmail,
|
||||
putUpdateMySocials,
|
||||
putUpdateMySound,
|
||||
putUpdateMyKeyboardShortcuts
|
||||
putUpdateMyTheme,
|
||||
putUpdateMyUsername,
|
||||
putVerifyCert
|
||||
} from '../../utils/ajax';
|
||||
import { certMap } from '../../resources/cert-and-project-map';
|
||||
import { completedChallengesSelector } from '..';
|
||||
import { completedChallengesSelector } from '../selectors';
|
||||
import {
|
||||
certTypes,
|
||||
certTypeIdMap
|
||||
} from '../../../../config/certification-settings';
|
||||
import {
|
||||
validateUsernameComplete,
|
||||
validateUsernameError,
|
||||
submitNewAboutComplete,
|
||||
submitNewAboutError,
|
||||
submitNewUsernameComplete,
|
||||
submitNewUsernameError,
|
||||
submitProfileUIComplete,
|
||||
submitProfileUIError,
|
||||
verifyCertComplete,
|
||||
verifyCertError,
|
||||
updateMySocialsComplete,
|
||||
updateMySocialsError,
|
||||
updateMyHonestyError,
|
||||
updateMyHonestyComplete,
|
||||
updateMyHonestyError,
|
||||
updateMyKeyboardShortcutsComplete,
|
||||
updateMyKeyboardShortcutsError,
|
||||
updateMyPortfolioComplete,
|
||||
updateMyPortfolioError,
|
||||
updateMyQuincyEmailComplete,
|
||||
updateMyQuincyEmailError,
|
||||
updateMyPortfolioError,
|
||||
updateMyPortfolioComplete,
|
||||
updateMyThemeComplete,
|
||||
updateMyThemeError,
|
||||
updateMySocialsComplete,
|
||||
updateMySocialsError,
|
||||
updateMySoundComplete,
|
||||
updateMySoundError,
|
||||
updateMyKeyboardShortcutsComplete,
|
||||
updateMyKeyboardShortcutsError
|
||||
} from './';
|
||||
updateMyThemeComplete,
|
||||
updateMyThemeError,
|
||||
validateUsernameComplete,
|
||||
validateUsernameError,
|
||||
verifyCertComplete,
|
||||
verifyCertError
|
||||
} from './actions';
|
||||
|
||||
function* submitNewAboutSaga({ payload }) {
|
||||
try {
|
||||
|
||||
@@ -2,10 +2,9 @@ import { call, put, takeEvery } from 'redux-saga/effects';
|
||||
import isEmail from 'validator/lib/isEmail';
|
||||
|
||||
import { createFlashMessage } from '../../components/Flash/redux';
|
||||
|
||||
import { putUserUpdateEmail } from '../../utils/ajax';
|
||||
import reallyWeirdErrorMessage from '../../utils/really-weird-error-message';
|
||||
import { updateMyEmailComplete, updateMyEmailError } from './';
|
||||
import { updateMyEmailComplete, updateMyEmailError } from './actions';
|
||||
|
||||
function* updateMyEmailSaga({ payload: email = '' }) {
|
||||
if (!email || !isEmail(email)) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { navigate } from 'gatsby';
|
||||
import { put, takeEvery, call } from 'redux-saga/effects';
|
||||
import { call, put, takeEvery } from 'redux-saga/effects';
|
||||
|
||||
import { createFlashMessage } from '../components/Flash/redux';
|
||||
import { getShowCert } from '../utils/ajax';
|
||||
import { showCertComplete, showCertError } from '.';
|
||||
import { showCertComplete, showCertError } from './actions';
|
||||
|
||||
function* getShowCertSaga({ payload: { username, certSlug } }) {
|
||||
try {
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
import { FlashApp, FlashMessageArg } from '../components/Flash/redux';
|
||||
import { MainApp } from '.';
|
||||
import { FlashMessages } from '../components/Flash/redux/flash-messages';
|
||||
import { ns as MainApp } from './action-types';
|
||||
|
||||
export const FlashApp = 'flash';
|
||||
|
||||
export type FlashMessageArg = {
|
||||
type: string;
|
||||
message: FlashMessages;
|
||||
variables?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export interface State {
|
||||
[FlashApp]: FlashState;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { ofType } from 'redux-observable';
|
||||
import { mapTo, filter } from 'rxjs/operators';
|
||||
import { filter, mapTo } from 'rxjs/operators';
|
||||
|
||||
import { actionTypes as types } from './action-types';
|
||||
import { serverStatusChange, isServerOnlineSelector } from './';
|
||||
import { serverStatusChange } from './actions';
|
||||
import { isServerOnlineSelector } from './selectors';
|
||||
|
||||
export default function updateCompleteEpic(action$, state$) {
|
||||
return action$.pipe(
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { call, put, takeEvery } from 'redux-saga/effects';
|
||||
|
||||
import { createFlashMessage } from '../components/Flash/redux';
|
||||
import { FlashMessages } from '../components/Flash/redux/flash-messages';
|
||||
import { deleteUserToken } from '../utils/ajax';
|
||||
import { deleteUserTokenComplete } from '.';
|
||||
import { deleteUserTokenComplete } from './actions';
|
||||
|
||||
const message = {
|
||||
deleted: {
|
||||
|
||||
@@ -4,11 +4,11 @@ import { createSelector } from 'reselect';
|
||||
|
||||
import { sortChallengeFiles } from '../../../../../utils/sort-challengefiles';
|
||||
import { ChallengeFile, ChallengeFiles } from '../../../redux/prop-types';
|
||||
import { toggleVisibleEditor } from '../redux/actions';
|
||||
import {
|
||||
toggleVisibleEditor,
|
||||
visibleEditorsSelector,
|
||||
challengeFilesSelector
|
||||
} from '../redux';
|
||||
} from '../redux/selectors';
|
||||
|
||||
type VisibleEditors = {
|
||||
[key: string]: boolean;
|
||||
|
||||
@@ -24,12 +24,12 @@ import { debounce } from 'lodash-es';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Loader } from '../../../components/helpers';
|
||||
import { Themes } from '../../../components/settings/theme';
|
||||
import { saveChallenge } from '../../../redux/actions';
|
||||
import {
|
||||
userSelector,
|
||||
saveChallenge,
|
||||
isDonationModalOpenSelector,
|
||||
isSignedInSelector
|
||||
} from '../../../redux';
|
||||
isSignedInSelector,
|
||||
userSelector
|
||||
} from '../../../redux/selectors';
|
||||
import {
|
||||
ChallengeFiles,
|
||||
Dimensions,
|
||||
@@ -45,24 +45,26 @@ import {
|
||||
isFinalProject
|
||||
} from '../../../../utils/challenge-types';
|
||||
import {
|
||||
canFocusEditorSelector,
|
||||
challengeMetaSelector,
|
||||
consoleOutputSelector,
|
||||
executeChallenge,
|
||||
saveEditorContent,
|
||||
setEditorFocusability,
|
||||
updateFile,
|
||||
challengeTestsSelector,
|
||||
submitChallenge,
|
||||
initTests,
|
||||
isResettingSelector,
|
||||
stopResetting,
|
||||
isProjectPreviewModalOpenSelector,
|
||||
openModal,
|
||||
isChallengeCompletedSelector,
|
||||
attemptsSelector,
|
||||
resetAttempts
|
||||
} from '../redux';
|
||||
} from '../redux/actions';
|
||||
import {
|
||||
attemptsSelector,
|
||||
canFocusEditorSelector,
|
||||
challengeMetaSelector,
|
||||
consoleOutputSelector,
|
||||
challengeTestsSelector,
|
||||
isResettingSelector,
|
||||
isProjectPreviewModalOpenSelector,
|
||||
isChallengeCompletedSelector
|
||||
} from '../redux/selectors';
|
||||
import GreenPass from '../../../assets/icons/green-pass';
|
||||
import LowerJaw from './lower-jaw';
|
||||
|
||||
|
||||
@@ -3,12 +3,15 @@ import { connect } from 'react-redux';
|
||||
import { ReflexContainer, ReflexElement, ReflexSplitter } from 'react-reflex';
|
||||
import { createSelector } from 'reselect';
|
||||
import { editor } from 'monaco-editor';
|
||||
import { isDonationModalOpenSelector, userSelector } from '../../../redux';
|
||||
import {
|
||||
userSelector,
|
||||
isDonationModalOpenSelector
|
||||
} from '../../../redux/selectors';
|
||||
import {
|
||||
canFocusEditorSelector,
|
||||
consoleOutputSelector,
|
||||
visibleEditorsSelector
|
||||
} from '../redux';
|
||||
} from '../redux/selectors';
|
||||
import { getTargetEditor } from '../utils/get-target-editor';
|
||||
import './editor.css';
|
||||
import {
|
||||
|
||||
@@ -38,21 +38,23 @@ import SidePanel from '../components/side-panel';
|
||||
import VideoModal from '../components/video-modal';
|
||||
import {
|
||||
cancelTests,
|
||||
challengeFilesSelector,
|
||||
challengeMounted,
|
||||
challengeTestsSelector,
|
||||
consoleOutputSelector,
|
||||
createFiles,
|
||||
executeChallenge,
|
||||
initConsole,
|
||||
initTests,
|
||||
isChallengeCompletedSelector,
|
||||
previewMounted,
|
||||
updateChallengeMeta,
|
||||
openModal,
|
||||
setEditorFocusability
|
||||
} from '../redux';
|
||||
import { savedChallengesSelector } from '../../../redux';
|
||||
} from '../redux/actions';
|
||||
import {
|
||||
challengeFilesSelector,
|
||||
challengeTestsSelector,
|
||||
consoleOutputSelector,
|
||||
isChallengeCompletedSelector
|
||||
} from '../redux/selectors';
|
||||
import { savedChallengesSelector } from '../../../redux/selectors';
|
||||
import { getGuideUrl } from '../utils';
|
||||
import MultifileEditor from './multifile-editor';
|
||||
import DesktopLayout from './desktop-layout';
|
||||
|
||||
@@ -19,22 +19,21 @@ import CompletionModal from '../components/completion-modal';
|
||||
import GreenPass from '../../../assets/icons/green-pass';
|
||||
import HelpModal from '../components/help-modal';
|
||||
import Hotkeys from '../components/Hotkeys';
|
||||
import { hideCodeAlly, tryToShowCodeAlly } from '../../../redux/actions';
|
||||
import {
|
||||
completedChallengesSelector,
|
||||
isSignedInSelector,
|
||||
hideCodeAlly,
|
||||
partiallyCompletedChallengesSelector,
|
||||
showCodeAllySelector,
|
||||
tryToShowCodeAlly,
|
||||
isSignedInSelector,
|
||||
userTokenSelector
|
||||
} from '../../../redux';
|
||||
} from '../../../redux/selectors';
|
||||
import {
|
||||
challengeMounted,
|
||||
isChallengeCompletedSelector,
|
||||
updateChallengeMeta,
|
||||
openModal,
|
||||
updateSolutionFormValues
|
||||
} from '../redux';
|
||||
} from '../redux/actions';
|
||||
import { isChallengeCompletedSelector } from '../redux/selectors';
|
||||
import { createFlashMessage } from '../../../components/Flash/redux';
|
||||
import {
|
||||
ChallengeNode,
|
||||
|
||||
@@ -5,15 +5,17 @@ import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { ChallengeFiles, Test, User } from '../../../redux/prop-types';
|
||||
|
||||
import { userSelector } from '../../../redux';
|
||||
import { userSelector } from '../../../redux/selectors';
|
||||
import {
|
||||
setEditorFocusability,
|
||||
submitChallenge,
|
||||
openModal
|
||||
} from '../redux/actions';
|
||||
import {
|
||||
canFocusEditorSelector,
|
||||
setEditorFocusability,
|
||||
challengeFilesSelector,
|
||||
submitChallenge,
|
||||
challengeTestsSelector,
|
||||
openModal
|
||||
} from '../redux';
|
||||
challengeTestsSelector
|
||||
} from '../redux/selectors';
|
||||
import './hotkeys.css';
|
||||
import { isFinalProject } from '../../../../utils/challenge-types';
|
||||
|
||||
|
||||
@@ -7,8 +7,9 @@ import { bindActionCreators, Dispatch } from 'redux';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
// Local Utilities
|
||||
import { executeGA } from '../../../redux';
|
||||
import { isResetModalOpenSelector, closeModal, resetChallenge } from '../redux';
|
||||
import { executeGA } from '../../../redux/actions';
|
||||
import { closeModal, resetChallenge } from '../redux/actions';
|
||||
import { isResetModalOpenSelector } from '../redux/selectors';
|
||||
|
||||
// Styles
|
||||
import './reset-modal.css';
|
||||
|
||||
@@ -12,22 +12,18 @@ import { createSelector } from 'reselect';
|
||||
import { dasherize } from '../../../../../utils/slugs';
|
||||
import { isFinalProject } from '../../../../utils/challenge-types';
|
||||
import Login from '../../../components/Header/components/Login';
|
||||
import {
|
||||
isSignedInSelector,
|
||||
executeGA,
|
||||
allowBlockDonationRequests
|
||||
} from '../../../redux';
|
||||
import { executeGA, allowBlockDonationRequests } from '../../../redux/actions';
|
||||
import { isSignedInSelector } from '../../../redux/selectors';
|
||||
import { AllChallengeNode, ChallengeFiles } from '../../../redux/prop-types';
|
||||
|
||||
import { closeModal, submitChallenge } from '../redux/actions';
|
||||
import {
|
||||
closeModal,
|
||||
submitChallenge,
|
||||
completedChallengesIds,
|
||||
isCompletionModalOpenSelector,
|
||||
successMessageSelector,
|
||||
challengeFilesSelector,
|
||||
challengeMetaSelector
|
||||
} from '../redux';
|
||||
} from '../redux/selectors';
|
||||
import CompletionModalBody from './completion-modal-body';
|
||||
|
||||
import './completion-modal.css';
|
||||
|
||||
@@ -7,8 +7,9 @@ import { connect } from 'react-redux';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
|
||||
import envData from '../../../../../config/env.json';
|
||||
import { executeGA } from '../../../redux';
|
||||
import { createQuestion, closeModal, isHelpModalOpenSelector } from '../redux';
|
||||
import { executeGA } from '../../../redux/actions';
|
||||
import { createQuestion, closeModal } from '../redux/actions';
|
||||
import { isHelpModalOpenSelector } from '../redux/selectors';
|
||||
|
||||
import './help-modal.css';
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Component, ReactElement } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { TFunction, withTranslation } from 'react-i18next';
|
||||
import { connect } from 'react-redux';
|
||||
import { storePortalDocument, removePortalDocument } from '../redux';
|
||||
import { storePortalDocument, removePortalDocument } from '../redux/actions';
|
||||
|
||||
interface PreviewPortalProps {
|
||||
children: ReactElement | null;
|
||||
|
||||
@@ -6,9 +6,9 @@ import type { CompletedChallenge } from '../../../redux/prop-types';
|
||||
import {
|
||||
closeModal,
|
||||
setEditorFocusability,
|
||||
isProjectPreviewModalOpenSelector,
|
||||
projectPreviewMounted
|
||||
} from '../redux';
|
||||
} from '../redux/actions';
|
||||
import { isProjectPreviewModalOpenSelector } from '../redux/selectors';
|
||||
import { projectPreviewId } from '../utils/frame';
|
||||
import Preview from './preview';
|
||||
|
||||
|
||||
@@ -5,9 +5,10 @@ import { connect } from 'react-redux';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import { closeModal, isShortcutsModalOpenSelector } from '../redux';
|
||||
import { updateMyKeyboardShortcuts } from '../../../redux/settings';
|
||||
import { userSelector } from '../../../redux';
|
||||
import { closeModal } from '../redux/actions';
|
||||
import { isShortcutsModalOpenSelector } from '../redux/selectors';
|
||||
import { updateMyKeyboardShortcuts } from '../../../redux/settings/actions';
|
||||
import { userSelector } from '../../../redux/selectors';
|
||||
import { User } from '../../../redux/prop-types';
|
||||
import KeyboardShortcutsSettings from '../../../components/settings/keyboard-shortcuts';
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { createSelector } from 'reselect';
|
||||
import { Test } from '../../../redux/prop-types';
|
||||
|
||||
import { mathJaxScriptLoader } from '../../../utils/script-loaders';
|
||||
import { challengeTestsSelector } from '../redux';
|
||||
import { challengeTestsSelector } from '../redux/selectors';
|
||||
import TestSuite from './test-suite';
|
||||
import ToolPanel from './tool-panel';
|
||||
|
||||
|
||||
@@ -11,9 +11,11 @@ import { createSelector } from 'reselect';
|
||||
import { challengeTypes } from '../../../../utils/challenge-types';
|
||||
|
||||
import './tool-panel.css';
|
||||
import { openModal, executeChallenge, challengeMetaSelector } from '../redux';
|
||||
import { openModal, executeChallenge } from '../redux/actions';
|
||||
import { challengeMetaSelector } from '../redux/selectors';
|
||||
|
||||
import { saveChallenge, isSignedInSelector } from '../../../redux';
|
||||
import { saveChallenge } from '../../../redux/actions';
|
||||
import { isSignedInSelector } from '../../../redux/selectors';
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
challengeMetaSelector,
|
||||
|
||||
@@ -4,8 +4,9 @@ import { withTranslation } from 'react-i18next';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
|
||||
import { executeGA } from '../../../redux';
|
||||
import { closeModal, isVideoModalOpenSelector } from '../redux';
|
||||
import { executeGA } from '../../../redux/actions';
|
||||
import { closeModal } from '../redux/actions';
|
||||
import { isVideoModalOpenSelector } from '../redux/selectors';
|
||||
|
||||
import './video-modal.css';
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import { createSelector } from 'reselect';
|
||||
|
||||
import Spacer from '../../../../components/helpers/spacer';
|
||||
import LearnLayout from '../../../../components/layouts/learn';
|
||||
import { isSignedInSelector } from '../../../../redux';
|
||||
import { isSignedInSelector } from '../../../../redux/selectors';
|
||||
import {
|
||||
ChallengeMeta,
|
||||
ChallengeNode,
|
||||
@@ -27,15 +27,17 @@ import Output from '../../components/output';
|
||||
import TestSuite from '../../components/test-suite';
|
||||
import {
|
||||
challengeMounted,
|
||||
challengeTestsSelector,
|
||||
consoleOutputSelector,
|
||||
executeChallenge,
|
||||
initConsole,
|
||||
initTests,
|
||||
isChallengeCompletedSelector,
|
||||
updateChallengeMeta,
|
||||
updateSolutionFormValues
|
||||
} from '../../redux';
|
||||
} from '../../redux/actions';
|
||||
import {
|
||||
challengeTestsSelector,
|
||||
consoleOutputSelector,
|
||||
isChallengeCompletedSelector
|
||||
} from '../../redux/selectors';
|
||||
import { getGuideUrl } from '../../utils';
|
||||
import SolutionForm from '../solution-form';
|
||||
import ProjectToolPanel from '../tool-panel';
|
||||
|
||||
@@ -18,11 +18,11 @@ import CompletionModal from '../../components/completion-modal';
|
||||
import HelpModal from '../../components/help-modal';
|
||||
import {
|
||||
challengeMounted,
|
||||
isChallengeCompletedSelector,
|
||||
updateChallengeMeta,
|
||||
openModal,
|
||||
updateSolutionFormValues
|
||||
} from '../../redux';
|
||||
} from '../../redux/actions';
|
||||
import { isChallengeCompletedSelector } from '../../redux/selectors';
|
||||
import { getGuideUrl } from '../../utils';
|
||||
import SolutionForm from '../solution-form';
|
||||
import ProjectToolPanel from '../tool-panel';
|
||||
|
||||
@@ -4,7 +4,7 @@ import { TFunction, withTranslation } from 'react-i18next';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
|
||||
import { openModal } from '../redux';
|
||||
import { openModal } from '../redux/actions';
|
||||
|
||||
import './tool-panel.css';
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { createTypes } from '../../../utils/create-types';
|
||||
|
||||
export const CURRENT_CHALLENGE_KEY = 'currentChallengeId';
|
||||
|
||||
export const ns = 'challenge';
|
||||
|
||||
export const actionTypes = createTypes(
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import { createAction } from 'redux-actions';
|
||||
|
||||
import { getLines } from '../../../../../utils/get-lines';
|
||||
import { actionTypes } from './action-types';
|
||||
|
||||
export const createFiles = createAction(
|
||||
actionTypes.createFiles,
|
||||
challengeFiles =>
|
||||
challengeFiles.map(challengeFile => ({
|
||||
...challengeFile,
|
||||
seed: challengeFile.contents.slice(),
|
||||
editableContents: getLines(
|
||||
challengeFile.contents,
|
||||
challengeFile.editableRegionBoundaries
|
||||
),
|
||||
seedEditableRegionBoundaries:
|
||||
challengeFile.editableRegionBoundaries?.slice()
|
||||
}))
|
||||
);
|
||||
|
||||
export const createQuestion = createAction(actionTypes.createQuestion);
|
||||
export const initTests = createAction(actionTypes.initTests);
|
||||
export const updateTests = createAction(actionTypes.updateTests);
|
||||
export const cancelTests = createAction(actionTypes.cancelTests);
|
||||
|
||||
export const initConsole = createAction(actionTypes.initConsole);
|
||||
export const initLogs = createAction(actionTypes.initLogs);
|
||||
export const updateChallengeMeta = createAction(
|
||||
actionTypes.updateChallengeMeta
|
||||
);
|
||||
export const updateFile = createAction(actionTypes.updateFile);
|
||||
export const updateConsole = createAction(actionTypes.updateConsole);
|
||||
export const updateLogs = createAction(actionTypes.updateLogs);
|
||||
export const updateJSEnabled = createAction(actionTypes.updateJSEnabled);
|
||||
export const updateSolutionFormValues = createAction(
|
||||
actionTypes.updateSolutionFormValues
|
||||
);
|
||||
export const updateSuccessMessage = createAction(
|
||||
actionTypes.updateSuccessMessage
|
||||
);
|
||||
|
||||
export const logsToConsole = createAction(actionTypes.logsToConsole);
|
||||
|
||||
export const lockCode = createAction(actionTypes.lockCode);
|
||||
export const unlockCode = createAction(actionTypes.unlockCode);
|
||||
export const disableBuildOnError = createAction(
|
||||
actionTypes.disableBuildOnError
|
||||
);
|
||||
export const storedCodeFound = createAction(actionTypes.storedCodeFound);
|
||||
export const noStoredCodeFound = createAction(actionTypes.noStoredCodeFound);
|
||||
export const saveEditorContent = createAction(actionTypes.saveEditorContent);
|
||||
|
||||
export const closeModal = createAction(actionTypes.closeModal);
|
||||
export const openModal = createAction(actionTypes.openModal);
|
||||
|
||||
export const previewMounted = createAction(actionTypes.previewMounted);
|
||||
export const projectPreviewMounted = createAction(
|
||||
actionTypes.projectPreviewMounted
|
||||
);
|
||||
|
||||
export const storePortalDocument = createAction(
|
||||
actionTypes.storePortalDocument
|
||||
);
|
||||
export const removePortalDocument = createAction(
|
||||
actionTypes.removePortalDocument
|
||||
);
|
||||
|
||||
export const challengeMounted = createAction(actionTypes.challengeMounted);
|
||||
export const checkChallenge = createAction(actionTypes.checkChallenge);
|
||||
export const executeChallenge = createAction(actionTypes.executeChallenge);
|
||||
export const resetChallenge = createAction(actionTypes.resetChallenge);
|
||||
export const stopResetting = createAction(actionTypes.stopResetting);
|
||||
export const submitChallenge = createAction(actionTypes.submitChallenge);
|
||||
export const resetAttempts = createAction(actionTypes.resetAttempts);
|
||||
|
||||
export const setEditorFocusability = createAction(
|
||||
actionTypes.setEditorFocusability
|
||||
);
|
||||
export const toggleVisibleEditor = createAction(
|
||||
actionTypes.toggleVisibleEditor
|
||||
);
|
||||
@@ -1,7 +1,8 @@
|
||||
import { ofType } from 'redux-observable';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
import { actionTypes } from './action-types';
|
||||
import { unlockCode } from './';
|
||||
import { unlockCode } from './actions';
|
||||
|
||||
function codeLockEpic(action$) {
|
||||
return action$.pipe(ofType(actionTypes.updateFile), map(unlockCode));
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
import { combineEpics, ofType } from 'redux-observable';
|
||||
import { of } from 'rxjs';
|
||||
import { filter, switchMap, map, tap, ignoreElements } from 'rxjs/operators';
|
||||
import { filter, ignoreElements, map, switchMap, tap } from 'rxjs/operators';
|
||||
import store from 'store';
|
||||
|
||||
import { setContent, isPoly } from '../../../../../utils/polyvinyl';
|
||||
import { isPoly, setContent } from '../../../../../utils/polyvinyl';
|
||||
import { createFlashMessage } from '../../../components/Flash/redux';
|
||||
import { FlashMessages } from '../../../components/Flash/redux/flash-messages';
|
||||
import { savedChallengesSelector } from '../../../redux/selectors';
|
||||
import { actionTypes as appTypes } from '../../../redux/action-types';
|
||||
import { savedChallengesSelector } from '../../../redux';
|
||||
|
||||
import { actionTypes } from './action-types';
|
||||
import { noStoredCodeFound, storedCodeFound } from './actions';
|
||||
import {
|
||||
storedCodeFound,
|
||||
noStoredCodeFound,
|
||||
isCodeLockedSelector,
|
||||
challengeFilesSelector,
|
||||
challengeMetaSelector
|
||||
} from './';
|
||||
challengeMetaSelector,
|
||||
isCodeLockedSelector
|
||||
} from './selectors';
|
||||
|
||||
const legacyPrefixes = [
|
||||
'Bonfire: ',
|
||||
|
||||
@@ -1,31 +1,28 @@
|
||||
import { navigate } from 'gatsby';
|
||||
import { omit } from 'lodash-es';
|
||||
import { ofType } from 'redux-observable';
|
||||
import { of, empty } from 'rxjs';
|
||||
import { switchMap, retry, catchError, concat, tap } from 'rxjs/operators';
|
||||
import { empty, of } from 'rxjs';
|
||||
import { catchError, concat, retry, switchMap, tap } from 'rxjs/operators';
|
||||
|
||||
import { challengeTypes, submitTypes } from '../../../../utils/challenge-types';
|
||||
import { actionTypes as submitActionTypes } from '../../../redux/action-types';
|
||||
import {
|
||||
userSelector,
|
||||
isSignedInSelector,
|
||||
submitComplete,
|
||||
updateComplete,
|
||||
updateFailed
|
||||
} from '../../../redux';
|
||||
|
||||
import postUpdate$ from '../utils/post-update';
|
||||
} from '../../../redux/actions';
|
||||
import { isSignedInSelector, userSelector } from '../../../redux/selectors';
|
||||
import { mapFilesToChallengeFiles } from '../../../utils/ajax';
|
||||
import { standardizeRequestBody } from '../../../utils/challenge-request-helpers';
|
||||
import { actionTypes as submitActionTypes } from '../../../redux/action-types';
|
||||
import postUpdate$ from '../utils/post-update';
|
||||
import { actionTypes } from './action-types';
|
||||
import { closeModal, updateSolutionFormValues } from './actions';
|
||||
import {
|
||||
projectFormValuesSelector,
|
||||
challengeFilesSelector,
|
||||
challengeMetaSelector,
|
||||
challengeTestsSelector,
|
||||
closeModal,
|
||||
challengeFilesSelector,
|
||||
updateSolutionFormValues
|
||||
} from './';
|
||||
projectFormValuesSelector
|
||||
} from './selectors';
|
||||
|
||||
function postChallenge(update, username) {
|
||||
const saveChallenge = postUpdate$(update).pipe(
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import i18next from 'i18next';
|
||||
import { ofType } from 'redux-observable';
|
||||
import { tap, mapTo } from 'rxjs/operators';
|
||||
import { mapTo, tap } from 'rxjs/operators';
|
||||
|
||||
import envData from '../../../../../config/env.json';
|
||||
import { transformEditorLink } from '../utils';
|
||||
import { actionTypes } from './action-types';
|
||||
import { closeModal } from './actions';
|
||||
import {
|
||||
closeModal,
|
||||
challengeFilesSelector,
|
||||
challengeMetaSelector,
|
||||
projectFormValuesSelector
|
||||
} from '../redux';
|
||||
import { transformEditorLink } from '../utils';
|
||||
import { actionTypes } from './action-types';
|
||||
} from './selectors';
|
||||
|
||||
const { forumLocation } = envData;
|
||||
|
||||
|
||||
@@ -2,9 +2,8 @@ import { put, takeEvery } from 'redux-saga/effects';
|
||||
import store from 'store';
|
||||
|
||||
import { randomCompliment } from '../../../utils/get-words';
|
||||
import { updateSuccessMessage } from './';
|
||||
|
||||
export const CURRENT_CHALLENGE_KEY = 'currentChallengeId';
|
||||
import { CURRENT_CHALLENGE_KEY } from './action-types';
|
||||
import { updateSuccessMessage } from './actions';
|
||||
|
||||
export function* currentChallengeSaga({ payload: id }) {
|
||||
yield store.set(CURRENT_CHALLENGE_KEY, id);
|
||||
|
||||
@@ -2,54 +2,56 @@ import i18next from 'i18next';
|
||||
import { escape } from 'lodash-es';
|
||||
import { channel } from 'redux-saga';
|
||||
import {
|
||||
delay,
|
||||
put,
|
||||
select,
|
||||
call,
|
||||
takeLatest,
|
||||
takeEvery,
|
||||
cancel,
|
||||
delay,
|
||||
fork,
|
||||
getContext,
|
||||
put,
|
||||
select,
|
||||
take,
|
||||
cancel
|
||||
takeEvery,
|
||||
takeLatest
|
||||
} from 'redux-saga/effects';
|
||||
|
||||
import { playTone } from '../../../utils/tone';
|
||||
import {
|
||||
buildChallenge,
|
||||
canBuildChallenge,
|
||||
getTestRunner,
|
||||
challengeHasPreview,
|
||||
updatePreview,
|
||||
updateProjectPreview,
|
||||
isJavaScriptChallenge,
|
||||
isLoopProtected
|
||||
} from '../utils/build';
|
||||
import { challengeTypes } from '../../../../utils/challenge-types';
|
||||
import { createFlashMessage } from '../../../components/Flash/redux';
|
||||
import { FlashMessages } from '../../../components/Flash/redux/flash-messages';
|
||||
import {
|
||||
standardizeRequestBody,
|
||||
getStringSizeInBytes,
|
||||
bodySizeFits,
|
||||
MAX_BODY_SIZE
|
||||
getStringSizeInBytes,
|
||||
MAX_BODY_SIZE,
|
||||
standardizeRequestBody
|
||||
} from '../../../utils/challenge-request-helpers';
|
||||
import { playTone } from '../../../utils/tone';
|
||||
import {
|
||||
buildChallenge,
|
||||
canBuildChallenge,
|
||||
challengeHasPreview,
|
||||
getTestRunner,
|
||||
isJavaScriptChallenge,
|
||||
isLoopProtected,
|
||||
updatePreview,
|
||||
updateProjectPreview
|
||||
} from '../utils/build';
|
||||
import { actionTypes } from './action-types';
|
||||
import {
|
||||
portalDocumentSelector,
|
||||
disableBuildOnError,
|
||||
initConsole,
|
||||
initLogs,
|
||||
logsToConsole,
|
||||
openModal,
|
||||
updateConsole,
|
||||
updateLogs,
|
||||
updateTests
|
||||
} from './actions';
|
||||
import {
|
||||
challengeDataSelector,
|
||||
challengeMetaSelector,
|
||||
challengeTestsSelector,
|
||||
initConsole,
|
||||
updateConsole,
|
||||
initLogs,
|
||||
updateLogs,
|
||||
logsToConsole,
|
||||
updateTests,
|
||||
openModal,
|
||||
isBuildEnabledSelector,
|
||||
disableBuildOnError
|
||||
} from './';
|
||||
portalDocumentSelector
|
||||
} from './selectors';
|
||||
|
||||
// How long before bailing out of a preview.
|
||||
const previewTimeout = 2500;
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { isEmpty } from 'lodash-es';
|
||||
import { createAction, handleActions } from 'redux-actions';
|
||||
import { handleActions } from 'redux-actions';
|
||||
|
||||
import { getLines } from '../../../../../utils/get-lines';
|
||||
import { challengeTypes } from '../../../../utils/challenge-types';
|
||||
import { completedChallengesSelector } from '../../../redux';
|
||||
import { getTargetEditor } from '../utils/get-target-editor';
|
||||
import { actionTypes, ns } from './action-types';
|
||||
import codeLockEpic from './code-lock-epic';
|
||||
@@ -60,167 +58,6 @@ export const sagas = [
|
||||
...createCurrentChallengeSaga(actionTypes)
|
||||
];
|
||||
|
||||
export const createFiles = createAction(
|
||||
actionTypes.createFiles,
|
||||
challengeFiles =>
|
||||
challengeFiles.map(challengeFile => ({
|
||||
...challengeFile,
|
||||
seed: challengeFile.contents.slice(),
|
||||
editableContents: getLines(
|
||||
challengeFile.contents,
|
||||
challengeFile.editableRegionBoundaries
|
||||
),
|
||||
seedEditableRegionBoundaries:
|
||||
challengeFile.editableRegionBoundaries?.slice()
|
||||
}))
|
||||
);
|
||||
|
||||
export const createQuestion = createAction(actionTypes.createQuestion);
|
||||
export const initTests = createAction(actionTypes.initTests);
|
||||
export const updateTests = createAction(actionTypes.updateTests);
|
||||
export const cancelTests = createAction(actionTypes.cancelTests);
|
||||
|
||||
export const initConsole = createAction(actionTypes.initConsole);
|
||||
export const initLogs = createAction(actionTypes.initLogs);
|
||||
export const updateChallengeMeta = createAction(
|
||||
actionTypes.updateChallengeMeta
|
||||
);
|
||||
export const updateFile = createAction(actionTypes.updateFile);
|
||||
export const updateConsole = createAction(actionTypes.updateConsole);
|
||||
export const updateLogs = createAction(actionTypes.updateLogs);
|
||||
export const updateJSEnabled = createAction(actionTypes.updateJSEnabled);
|
||||
export const updateSolutionFormValues = createAction(
|
||||
actionTypes.updateSolutionFormValues
|
||||
);
|
||||
export const updateSuccessMessage = createAction(
|
||||
actionTypes.updateSuccessMessage
|
||||
);
|
||||
|
||||
export const logsToConsole = createAction(actionTypes.logsToConsole);
|
||||
|
||||
export const lockCode = createAction(actionTypes.lockCode);
|
||||
export const unlockCode = createAction(actionTypes.unlockCode);
|
||||
export const disableBuildOnError = createAction(
|
||||
actionTypes.disableBuildOnError
|
||||
);
|
||||
export const storedCodeFound = createAction(actionTypes.storedCodeFound);
|
||||
export const noStoredCodeFound = createAction(actionTypes.noStoredCodeFound);
|
||||
export const saveEditorContent = createAction(actionTypes.saveEditorContent);
|
||||
|
||||
export const closeModal = createAction(actionTypes.closeModal);
|
||||
export const openModal = createAction(actionTypes.openModal);
|
||||
|
||||
export const previewMounted = createAction(actionTypes.previewMounted);
|
||||
export const projectPreviewMounted = createAction(
|
||||
actionTypes.projectPreviewMounted
|
||||
);
|
||||
|
||||
export const storePortalDocument = createAction(
|
||||
actionTypes.storePortalDocument
|
||||
);
|
||||
export const removePortalDocument = createAction(
|
||||
actionTypes.removePortalDocument
|
||||
);
|
||||
|
||||
export const challengeMounted = createAction(actionTypes.challengeMounted);
|
||||
export const checkChallenge = createAction(actionTypes.checkChallenge);
|
||||
export const executeChallenge = createAction(actionTypes.executeChallenge);
|
||||
export const resetChallenge = createAction(actionTypes.resetChallenge);
|
||||
export const stopResetting = createAction(actionTypes.stopResetting);
|
||||
export const submitChallenge = createAction(actionTypes.submitChallenge);
|
||||
export const resetAttempts = createAction(actionTypes.resetAttempts);
|
||||
|
||||
export const setEditorFocusability = createAction(
|
||||
actionTypes.setEditorFocusability
|
||||
);
|
||||
export const toggleVisibleEditor = createAction(
|
||||
actionTypes.toggleVisibleEditor
|
||||
);
|
||||
|
||||
export const currentTabSelector = state => state[ns].currentTab;
|
||||
export const challengeFilesSelector = state => state[ns].challengeFiles;
|
||||
export const challengeMetaSelector = state => state[ns].challengeMeta;
|
||||
export const challengeTestsSelector = state => state[ns].challengeTests;
|
||||
export const consoleOutputSelector = state => state[ns].consoleOut;
|
||||
export const completedChallengesIds = state =>
|
||||
completedChallengesSelector(state).map(node => node.id);
|
||||
export const isChallengeCompletedSelector = state => {
|
||||
const completedChallenges = completedChallengesSelector(state);
|
||||
const { id: currentChallengeId } = challengeMetaSelector(state);
|
||||
return completedChallenges.some(({ id }) => id === currentChallengeId);
|
||||
};
|
||||
export const isCodeLockedSelector = state => state[ns].isCodeLocked;
|
||||
export const isCompletionModalOpenSelector = state =>
|
||||
state[ns].modal.completion;
|
||||
export const isHelpModalOpenSelector = state => state[ns].modal.help;
|
||||
export const isVideoModalOpenSelector = state => state[ns].modal.video;
|
||||
export const isResetModalOpenSelector = state => state[ns].modal.reset;
|
||||
export const isProjectPreviewModalOpenSelector = state =>
|
||||
state[ns].modal.projectPreview;
|
||||
export const isShortcutsModalOpenSelector = state => state[ns].modal.shortcuts;
|
||||
export const isResettingSelector = state => state[ns].isResetting;
|
||||
|
||||
export const isBuildEnabledSelector = state => state[ns].isBuildEnabled;
|
||||
export const successMessageSelector = state => state[ns].successMessage;
|
||||
|
||||
export const projectFormValuesSelector = state =>
|
||||
state[ns].projectFormValues || {};
|
||||
|
||||
export const portalDocumentSelector = state => state[ns].portalDocument;
|
||||
|
||||
export const challengeDataSelector = state => {
|
||||
const { challengeType } = challengeMetaSelector(state);
|
||||
let challengeData = { challengeType };
|
||||
if (
|
||||
challengeType === challengeTypes.js ||
|
||||
challengeType === challengeTypes.jsProject
|
||||
) {
|
||||
challengeData = {
|
||||
...challengeData,
|
||||
challengeFiles: challengeFilesSelector(state)
|
||||
};
|
||||
} else if (challengeType === challengeTypes.backend) {
|
||||
const { solution: url = {} } = projectFormValuesSelector(state);
|
||||
challengeData = {
|
||||
...challengeData,
|
||||
url
|
||||
};
|
||||
} else if (
|
||||
challengeType === challengeTypes.backEndProject ||
|
||||
challengeType === challengeTypes.pythonProject
|
||||
) {
|
||||
const values = projectFormValuesSelector(state);
|
||||
const { solution: url } = values;
|
||||
challengeData = {
|
||||
...challengeData,
|
||||
...values,
|
||||
url
|
||||
};
|
||||
} else if (challengeType === challengeTypes.frontEndProject) {
|
||||
challengeData = {
|
||||
...challengeData,
|
||||
...projectFormValuesSelector(state)
|
||||
};
|
||||
} else if (
|
||||
challengeType === challengeTypes.html ||
|
||||
challengeType === challengeTypes.modern ||
|
||||
challengeType === challengeTypes.multifileCertProject
|
||||
) {
|
||||
const { required = [], template = '' } = challengeMetaSelector(state);
|
||||
challengeData = {
|
||||
...challengeData,
|
||||
challengeFiles: challengeFilesSelector(state),
|
||||
required,
|
||||
template
|
||||
};
|
||||
}
|
||||
return challengeData;
|
||||
};
|
||||
|
||||
export const attemptsSelector = state => state[ns].attempts;
|
||||
export const canFocusEditorSelector = state => state[ns].canFocusEditor;
|
||||
export const visibleEditorsSelector = state => state[ns].visibleEditors;
|
||||
|
||||
export const reducer = handleActions(
|
||||
{
|
||||
[actionTypes.createFiles]: (state, { payload }) => ({
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import { challengeTypes } from '../../../../utils/challenge-types';
|
||||
import { completedChallengesSelector } from '../../../redux/selectors';
|
||||
import { ns } from './action-types';
|
||||
|
||||
export const currentTabSelector = state => state[ns].currentTab;
|
||||
export const challengeFilesSelector = state => state[ns].challengeFiles;
|
||||
export const challengeMetaSelector = state => state[ns].challengeMeta;
|
||||
export const challengeTestsSelector = state => state[ns].challengeTests;
|
||||
export const consoleOutputSelector = state => state[ns].consoleOut;
|
||||
export const completedChallengesIds = state =>
|
||||
completedChallengesSelector(state).map(node => node.id);
|
||||
export const isChallengeCompletedSelector = state => {
|
||||
const completedChallenges = completedChallengesSelector(state);
|
||||
const { id: currentChallengeId } = challengeMetaSelector(state);
|
||||
return completedChallenges.some(({ id }) => id === currentChallengeId);
|
||||
};
|
||||
export const isCodeLockedSelector = state => state[ns].isCodeLocked;
|
||||
export const isCompletionModalOpenSelector = state =>
|
||||
state[ns].modal.completion;
|
||||
export const isHelpModalOpenSelector = state => state[ns].modal.help;
|
||||
export const isVideoModalOpenSelector = state => state[ns].modal.video;
|
||||
export const isResetModalOpenSelector = state => state[ns].modal.reset;
|
||||
export const isProjectPreviewModalOpenSelector = state =>
|
||||
state[ns].modal.projectPreview;
|
||||
export const isShortcutsModalOpenSelector = state => state[ns].modal.shortcuts;
|
||||
export const isResettingSelector = state => state[ns].isResetting;
|
||||
|
||||
export const isBuildEnabledSelector = state => state[ns].isBuildEnabled;
|
||||
export const successMessageSelector = state => state[ns].successMessage;
|
||||
|
||||
export const projectFormValuesSelector = state =>
|
||||
state[ns].projectFormValues || {};
|
||||
|
||||
export const portalDocumentSelector = state => state[ns].portalDocument;
|
||||
|
||||
export const challengeDataSelector = state => {
|
||||
const { challengeType } = challengeMetaSelector(state);
|
||||
let challengeData = { challengeType };
|
||||
if (
|
||||
challengeType === challengeTypes.js ||
|
||||
challengeType === challengeTypes.jsProject
|
||||
) {
|
||||
challengeData = {
|
||||
...challengeData,
|
||||
challengeFiles: challengeFilesSelector(state)
|
||||
};
|
||||
} else if (challengeType === challengeTypes.backend) {
|
||||
const { solution: url = {} } = projectFormValuesSelector(state);
|
||||
challengeData = {
|
||||
...challengeData,
|
||||
url
|
||||
};
|
||||
} else if (
|
||||
challengeType === challengeTypes.backEndProject ||
|
||||
challengeType === challengeTypes.pythonProject
|
||||
) {
|
||||
const values = projectFormValuesSelector(state);
|
||||
const { solution: url } = values;
|
||||
challengeData = {
|
||||
...challengeData,
|
||||
...values,
|
||||
url
|
||||
};
|
||||
} else if (challengeType === challengeTypes.frontEndProject) {
|
||||
challengeData = {
|
||||
...challengeData,
|
||||
...projectFormValuesSelector(state)
|
||||
};
|
||||
} else if (
|
||||
challengeType === challengeTypes.html ||
|
||||
challengeType === challengeTypes.modern ||
|
||||
challengeType === challengeTypes.multifileCertProject
|
||||
) {
|
||||
const { required = [], template = '' } = challengeMetaSelector(state);
|
||||
challengeData = {
|
||||
...challengeData,
|
||||
challengeFiles: challengeFilesSelector(state),
|
||||
required,
|
||||
template
|
||||
};
|
||||
}
|
||||
return challengeData;
|
||||
};
|
||||
|
||||
export const attemptsSelector = state => state[ns].attempts;
|
||||
export const canFocusEditorSelector = state => state[ns].canFocusEditor;
|
||||
export const visibleEditorsSelector = state => state[ns].visibleEditors;
|
||||
@@ -22,12 +22,12 @@ import ChallengeTitle from '../components/challenge-title';
|
||||
import CompletionModal from '../components/completion-modal';
|
||||
import PrismFormatted from '../components/prism-formatted';
|
||||
import {
|
||||
isChallengeCompletedSelector,
|
||||
challengeMounted,
|
||||
updateChallengeMeta,
|
||||
openModal,
|
||||
updateSolutionFormValues
|
||||
} from '../redux';
|
||||
} from '../redux/actions';
|
||||
import { isChallengeCompletedSelector } from '../redux/selectors';
|
||||
|
||||
// Styles
|
||||
import './show.css';
|
||||
|
||||
@@ -13,7 +13,8 @@ import DropDown from '../../../assets/icons/dropdown';
|
||||
import GreenNotCompleted from '../../../assets/icons/green-not-completed';
|
||||
import GreenPass from '../../../assets/icons/green-pass';
|
||||
import { Link, Spacer } from '../../../components/helpers';
|
||||
import { completedChallengesSelector, executeGA } from '../../../redux';
|
||||
import { executeGA } from '../../../redux/actions';
|
||||
import { completedChallengesSelector } from '../../../redux/selectors';
|
||||
import { ChallengeNode, CompletedChallenge } from '../../../redux/prop-types';
|
||||
import { playTone } from '../../../utils/tone';
|
||||
import { makeExpandedBlockSelector, toggleBlock } from '../redux';
|
||||
|
||||
@@ -12,12 +12,12 @@ import {
|
||||
import { createFlashMessage } from '../../../components/Flash/redux';
|
||||
import { FlashMessages } from '../../../components/Flash/redux/flash-messages';
|
||||
import {
|
||||
userFetchStateSelector,
|
||||
isSignedInSelector,
|
||||
userFetchStateSelector,
|
||||
currentCertsSelector
|
||||
} from '../../../redux';
|
||||
} from '../../../redux/selectors';
|
||||
import { User, Steps } from '../../../redux/prop-types';
|
||||
import { verifyCert } from '../../../redux/settings';
|
||||
import { verifyCert } from '../../../redux/settings/actions';
|
||||
import { certMap } from '../../../resources/cert-and-project-map';
|
||||
|
||||
interface CertChallengeProps {
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { Dispatch } from 'redux';
|
||||
|
||||
import GreenNotCompleted from '../../../assets/icons/green-not-completed';
|
||||
import GreenPass from '../../../assets/icons/green-pass';
|
||||
import { executeGA } from '../../../redux';
|
||||
import { executeGA } from '../../../redux/actions';
|
||||
import { SuperBlocks } from '../../../../../config/certification-settings';
|
||||
import { ExecuteGaArg } from '../../../pages/donate';
|
||||
import { ChallengeWithCompletedNode } from '../../../redux/prop-types';
|
||||
|
||||
@@ -15,14 +15,14 @@ import DonateModal from '../../components/Donation/donation-modal';
|
||||
import Login from '../../components/Header/components/Login';
|
||||
import Map from '../../components/Map';
|
||||
import { Spacer } from '../../components/helpers';
|
||||
import { tryToShowDonationModal } from '../../redux/actions';
|
||||
import {
|
||||
isSignedInSelector,
|
||||
userSelector,
|
||||
currentChallengeIdSelector,
|
||||
userFetchStateSelector,
|
||||
signInLoadingSelector,
|
||||
isSignedInSelector,
|
||||
tryToShowDonationModal,
|
||||
userSelector
|
||||
} from '../../redux';
|
||||
signInLoadingSelector
|
||||
} from '../../redux/selectors';
|
||||
import { MarkdownRemark, AllChallengeNode, User } from '../../redux/prop-types';
|
||||
import Block from './components/block';
|
||||
import CertChallenge from './components/cert-challenge';
|
||||
|
||||
@@ -9,6 +9,7 @@ import { SuperBlocks } from '../../config/certification-settings';
|
||||
import { blockNameify } from '../../utils/block-nameify';
|
||||
import { createStepFile } from './utils';
|
||||
import { getSuperBlockSubPath } from './fs-utils';
|
||||
import { Meta } from './helpers/project-metadata';
|
||||
|
||||
const helpCategories = ['HTML-CSS', 'JavaScript', 'Python'] as const;
|
||||
|
||||
@@ -23,20 +24,6 @@ type SuperBlockInfo = {
|
||||
|
||||
type IntroJson = Record<SuperBlocks, SuperBlockInfo>;
|
||||
|
||||
export type Meta = {
|
||||
name: string;
|
||||
isUpcomingChange: boolean;
|
||||
dashedName: string;
|
||||
order: number;
|
||||
time: string;
|
||||
template: string;
|
||||
required: string[];
|
||||
superBlock: string;
|
||||
superOrder: number;
|
||||
isBeta: boolean;
|
||||
challengeOrder: string[][];
|
||||
};
|
||||
|
||||
interface CreateProjectArgs {
|
||||
superBlock: SuperBlocks;
|
||||
block: string;
|
||||
|
||||
@@ -1,9 +1,22 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import glob from 'glob';
|
||||
import { Meta } from '../create-project';
|
||||
import { getProjectName, getProjectPath } from './get-project-info';
|
||||
|
||||
export type Meta = {
|
||||
name: string;
|
||||
isUpcomingChange: boolean;
|
||||
dashedName: string;
|
||||
order: number;
|
||||
time: string;
|
||||
template: string;
|
||||
required: string[];
|
||||
superBlock: string;
|
||||
superOrder: number;
|
||||
isBeta: boolean;
|
||||
challengeOrder: string[][];
|
||||
};
|
||||
|
||||
function getMetaData(): Meta {
|
||||
const metaData = fs.readFileSync(getProjectMetaPath(), 'utf8');
|
||||
return JSON.parse(metaData) as Meta;
|
||||
|
||||
Reference in New Issue
Block a user