chore(client): tidy Flash by tidying tone (#44320)

* chore(client): tidy Flash by tidying tone

* add store import

* fix enum, remove default switch case

Co-authored-by: Nicholas Carrigan <nhcarrigan@gmail.com>

* typo galore

Co-authored-by: Nicholas Carrigan (he/him) <nhcarrigan@gmail.com>

* rejig everything, because refactoring is fun 🙃

* refactor: DRY playTone

* fix url to foss library

Co-authored-by: Nicholas Carrigan <nhcarrigan@gmail.com>

* alphabetasize FlashMessage enum

* add all FlashMessages to tone/index.ts

* remove redundant type

* my code is correctnpm run develop:client

* fix: remove circular dependency

* fix: typo

* remove logs, play special tones for nightmode

* play sound on challengeComplete

Co-authored-by: Nicholas Carrigan (he/him) <nhcarrigan@gmail.com>

Co-authored-by: Nicholas Carrigan <nhcarrigan@gmail.com>
Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
Shaun Hamilton
2021-12-01 18:45:17 +00:00
committed by GitHub
parent edf3185b2b
commit 23e241bbc0
29 changed files with 216 additions and 145 deletions

View File

@ -14,6 +14,7 @@ import DonateForm from '../components/Donation/donate-form';
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 {
showCertSelector,
showCertFetchStateSelector,
@ -268,7 +269,7 @@ const ShowCertification = (props: ShowCertificationProps): JSX.Element => {
<Row>
<Col lg={8} lgOffset={2} sm={10} smOffset={1} xs={12}>
<DonateForm
defaultTheme='default'
defaultTheme={Themes.Default}
handleProcessing={handleProcessing}
isMinimalForm={true}
/>

View File

@ -16,6 +16,7 @@ import Honesty from '../components/settings/honesty';
import Internet from '../components/settings/internet';
import Portfolio from '../components/settings/portfolio';
import Privacy from '../components/settings/privacy';
import { Themes } from '../components/settings/theme';
import WebhookToken from '../components/settings/webhook-token';
import {
signInLoadingSelector,
@ -35,7 +36,7 @@ interface ShowSettingsProps {
navigate: (location: string) => void;
showLoading: boolean;
submitNewAbout: () => void;
toggleNightMode: (theme: string) => void;
toggleNightMode: (theme: Themes) => void;
toggleSoundMode: (sound: boolean) => void;
updateInternetSettings: () => void;
updateIsHonest: () => void;
@ -61,7 +62,7 @@ const mapDispatchToProps = {
createFlashMessage,
navigate,
submitNewAbout,
toggleNightMode: (theme: string) => updateUserFlag({ theme }),
toggleNightMode: (theme: Themes) => updateUserFlag({ theme }),
toggleSoundMode: (sound: boolean) => updateUserFlag({ sound }),
updateInternetSettings: updateUserFlag,
updateIsHonest: updateUserFlag,

View File

@ -26,6 +26,7 @@ import {
postChargeStripeCard
} from '../../redux';
import Spacer from '../helpers/spacer';
import { Themes } from '../settings/theme';
import DonateCompletion from './donate-completion';
import PatreonButton from './patreon-button';
import type { AddDonationData } from './paypal-button';
@ -63,7 +64,7 @@ type DonateFormProps = {
duration: string;
handleAuthentication: HandleAuthentication;
}) => void;
defaultTheme?: string;
defaultTheme?: Themes;
email: string;
handleProcessing: (duration: string, amount: number, action: string) => void;
donationFormState: DonateFormState;
@ -74,7 +75,7 @@ type DonateFormProps = {
label: string,
{ usd, hours }?: { usd?: string | number; hours?: string }
) => string;
theme: string;
theme: Themes;
updateDonationFormState: (state: AddDonationData) => unknown;
};
@ -87,7 +88,7 @@ const mapStateToProps = createSelector(
showLoading: DonateFormProps['showLoading'],
isSignedIn: DonateFormProps['isSignedIn'],
donationFormState: DonateFormState,
{ email, theme }: { email: string; theme: string }
{ email, theme }: { email: string; theme: Themes }
) => ({
isSignedIn,
showLoading,

View File

@ -6,7 +6,6 @@ import { connect } from 'react-redux';
import { goToAnchor } from 'react-scrollable-anchor';
import { bindActionCreators, Dispatch, AnyAction } from 'redux';
import { createSelector } from 'reselect';
import store from 'store';
import { modalDefaultDonation } from '../../../../config/donation-settings';
import Cup from '../../assets/icons/cup';
import Heart from '../../assets/icons/heart';
@ -18,6 +17,7 @@ import {
executeGA
} from '../../redux';
import { isLocationSuperBlock } from '../../utils/path-parsers';
import { playTone } from '../../utils/tone';
import { Spacer } from '../helpers';
import DonateForm from './donate-form';
@ -76,16 +76,7 @@ function DonateModal({
useEffect(() => {
if (show) {
const playSound = store.get('fcc-sound') as boolean | undefined;
if (playSound) {
void import('tone').then(tone => {
const player = new tone.Player(
'https://campfire-mode.freecodecamp.org/donate.mp3'
).toDestination();
if (tone.context.state !== 'running') void tone.context.resume();
player.autostart = playSound;
});
}
void playTone('donation');
executeGA({ type: 'modal', data: '/donation-modal' });
executeGA({
type: 'event',

View File

@ -1,5 +1,6 @@
import { render } from '@testing-library/react';
import React from 'react';
import { Themes } from '../settings/theme';
import { PaypalButton } from './paypal-button';
@ -11,7 +12,7 @@ const commonProps = {
onDonationStateChange: () => null,
isPaypalLoading: true,
t: jest.fn(),
theme: 'night',
theme: Themes.Night,
handlePaymentButtonLoad: jest.fn(),
isMinimalForm: true
};

View File

@ -13,6 +13,7 @@ import {
} from '../../../../config/donation-settings';
import envData from '../../../../config/env.json';
import { signInLoadingSelector, userSelector } from '../../redux';
import { Themes } from '../settings/theme';
import PayPalButtonScriptLoader from './paypal-button-script-loader';
type PaypalButtonProps = {
@ -41,7 +42,7 @@ type PaypalButtonProps = {
skipAddDonation?: boolean;
t: (label: string) => string;
ref?: Ref<PaypalButton>;
theme: string;
theme: Themes;
isSubscription?: boolean;
handlePaymentButtonLoad: (provider: 'stripe' | 'paypal') => void;
isMinimalForm: boolean | undefined;
@ -131,7 +132,7 @@ export class PaypalButton extends Component<
const { duration, planId, amount } = this.state;
const { t, theme, isPaypalLoading, isMinimalForm } = this.props;
const isSubscription = duration !== 'onetime';
const buttonColor = theme === 'night' ? 'white' : 'gold';
const buttonColor = theme === Themes.Night ? 'white' : 'gold';
if (!paypalClientId) {
return null;
}

View File

@ -16,6 +16,7 @@ import type {
import React, { useState } from 'react';
import envData from '../../../../config/env.json';
import { Themes } from '../settings/theme';
import { AddDonationData } from './paypal-button';
const { stripePublicKey }: { stripePublicKey: string | null } = envData;
@ -32,7 +33,7 @@ interface FormPropTypes {
handleAuthentication: HandleAuthentication
) => void;
t: (label: string) => string;
theme: string;
theme: Themes;
processing: boolean;
}
@ -92,7 +93,7 @@ const StripeCardForm = ({
style: {
base: {
fontSize: '18px',
color: `${theme === 'night' ? '#fff' : '#0a0a23'}`,
color: `${theme === Themes.Night ? '#fff' : '#0a0a23'}`,
'::placeholder': {
color: `#858591`
}

View File

@ -7,6 +7,7 @@ import { Stripe, loadStripe } from '@stripe/stripe-js';
import type { Token, PaymentRequest } from '@stripe/stripe-js';
import React, { useState, useEffect } from 'react';
import envData from '../../../../config/env.json';
import { Themes } from '../settings/theme';
import { AddDonationData } from './paypal-button';
const { stripePublicKey }: { stripePublicKey: string | null } = envData;
@ -14,7 +15,7 @@ const { stripePublicKey }: { stripePublicKey: string | null } = envData;
interface WrapperProps {
label: string;
amount: number;
theme: string;
theme: Themes;
postStripeDonation: (
token: Token,
payerEmail: string | undefined,
@ -98,7 +99,7 @@ const WalletsButton = ({
style: {
paymentRequestButton: {
type: 'default',
theme: theme === 'night' ? 'light' : 'dark',
theme: theme === Themes.Night ? 'light' : 'dark',
height: '43px'
}
},

View File

@ -0,0 +1,35 @@
export enum FlashMessages {
AccountDeleted = 'flash.account-deleted',
AddNameSuccess = 'flash.add-name',
AlreadyClaimed = 'flash.already-claimed',
CertClaimSuccess = 'flash.cert-claim-success',
CertificateMissing = 'flash.certificate-missing',
CertsPrivate = 'flash.certs-private',
CreateTokenErr = 'flash.create-token-err',
DeleteTokenErr = 'flash.delete-token-err',
EmailValid = 'flash.email-valid',
HonestFirst = 'flash.honest-first',
IncompleteSteps = 'flash.incomplete-steps',
NameNeeded = 'flash.name-needed',
None = '',
NotEligible = 'flash.not-eligible',
NotHonest = 'flash.not-honest',
NotRight = 'flash.not-right',
ProfilePrivate = 'flash.profile-private',
ProgressReset = 'flash.progress-reset',
ProvideUsername = 'flash.provide-username',
ReallyWeird = 'flash.really-weird',
ReportSent = 'flash.report-sent',
SigninSuccess = 'flash.signin-success',
TokenCreated = 'flash.token-created',
TokenDeleted = 'flash.token-deleted',
UpdatedPreferences = 'flash.updated-preferences',
UsernameNotFound = 'flash.username-not-found',
UsernameTaken = 'flash.username-taken',
UsernameUpdated = 'flash.username-updated',
UsernameUsed = 'flash.username-used',
UserNotCertified = 'flash.user-not-certified',
WrongName = 'flash.wrong-name',
WrongUpdating = 'flash.wrong-updating',
WentWrong = 'flash.went-wrong'
}

View File

@ -1,17 +1,12 @@
import { nanoid } from 'nanoid';
import store from 'store';
import { FlashState, State } from '../../../redux/types';
import { playTone } from '../../../utils/tone';
import { Themes } from '../../settings/theme';
import { FlashMessages } from './flash-messages';
export const FlashApp = 'flash';
const initialState = {
message: {
id: '',
type: '',
message: ''
}
};
export const sagas = [];
export const flashMessageSelector = (state: State): FlashState['message'] =>
@ -26,32 +21,26 @@ enum FlashActionTypes {
export type FlashMessageArg = {
type: string;
message: string;
message: FlashMessages;
variables?: Record<string, unknown>;
};
const initialState = {
message: {
id: '',
type: '',
message: FlashMessages.None
}
};
export const createFlashMessage = (
flash: FlashMessageArg
): ReducerPayload<FlashActionTypes.CreateFlashMessage> => {
const playSound = store.get('fcc-sound') as boolean | undefined;
if (playSound) {
void import('tone').then(tone => {
if (tone.context.state !== 'running') {
void tone.context.resume();
}
if (flash.message === 'flash.incomplete-steps') {
const player = new tone.Player(
'https://campfire-mode.freecodecamp.org/try-again.mp3'
).toDestination();
player.autostart = playSound;
}
if (flash.message === 'flash.cert-claim-success') {
const player = new tone.Player(
'https://campfire-mode.freecodecamp.org/cert.mp3'
).toDestination();
player.autostart = playSound;
}
});
// Nightmode theme has special tones
if (flash.variables?.theme) {
void playTone(flash.variables.theme as Themes);
} else if (flash.message !== FlashMessages.None) {
void playTone(flash.message);
}
return {
type: FlashActionTypes.CreateFlashMessage,

View File

@ -28,6 +28,7 @@ import { hardGoTo as navigate } from '../../../redux';
import { updateUserFlag } from '../../../redux/settings';
import createLanguageRedirect from '../../create-language-redirect';
import { Link } from '../../helpers';
import { Themes } from '../../settings/theme';
const { clientLocale, radioLocation, apiLocation } = envData;
@ -46,7 +47,7 @@ export interface NavLinksProps {
const mapDispatchToProps = {
navigate,
toggleNightMode: (theme: unknown) => updateUserFlag({ theme })
toggleNightMode: (theme: Themes) => updateUserFlag({ theme })
};
export class NavLinks extends Component<NavLinksProps, {}> {
@ -57,8 +58,10 @@ export class NavLinks extends Component<NavLinksProps, {}> {
this.handleLanguageChange = this.handleLanguageChange.bind(this);
}
toggleTheme(currentTheme = 'default', toggleNightMode: any) {
toggleNightMode(currentTheme === 'night' ? 'default' : 'night');
toggleTheme(currentTheme = Themes.Default, toggleNightMode: any) {
toggleNightMode(
currentTheme === Themes.Night ? Themes.Default : Themes.Night
);
}
handleLanguageChange = (
@ -175,7 +178,7 @@ export class NavLinks extends Component<NavLinksProps, {}> {
{username ? (
<>
<span>{t('settings.labels.night-mode')}</span>
{theme === 'night' ? (
{theme === Themes.Night ? (
<FontAwesomeIcon icon={faCheckSquare} />
) : (
<FontAwesomeIcon icon={faSquare} />

View File

@ -12,6 +12,7 @@ import {
availableLangs,
langDisplayNames
} from '../../../../config/i18n/all-langs';
import { Themes } from '../settings/theme';
import AuthOrProfile from './components/auth-or-profile';
import { NavLinks } from './components/nav-links';
import { UniversalNav } from './components/universal-nav';
@ -50,12 +51,12 @@ describe('<NavLinks />', () => {
user: {
isDonating: false,
username: null,
theme: 'default'
theme: Themes.Default
},
i18n: {
language: 'en'
},
toggleNightMode: (theme: string) => theme,
toggleNightMode: (theme: Themes) => theme,
t: t
};
const utils = ShallowRenderer.createRenderer();
@ -81,13 +82,13 @@ describe('<NavLinks />', () => {
user: {
isDonating: false,
username: 'nhcarrigan',
theme: 'default'
theme: Themes.Default
},
i18n: {
language: 'en'
},
t: t,
toggleNightMode: (theme: string) => theme
toggleNightMode: (theme: Themes) => theme
};
const utils = ShallowRenderer.createRenderer();
utils.render(<NavLinks {...landingPageProps} />);
@ -111,13 +112,13 @@ describe('<NavLinks />', () => {
user: {
isDonating: true,
username: 'moT01',
theme: 'default'
theme: Themes.Default
},
i18n: {
language: 'en'
},
t: t,
toggleNightMode: (theme: string) => theme
toggleNightMode: (theme: Themes) => theme
};
const utils = ShallowRenderer.createRenderer();
utils.render(<NavLinks {...landingPageProps} />);
@ -143,13 +144,13 @@ describe('<NavLinks />', () => {
user: {
isDonating: true,
username: 'moT01',
theme: 'default'
theme: Themes.Default
},
i18n: {
language: 'en'
},
t: t,
toggleNightMode: (theme: string) => theme
toggleNightMode: (theme: Themes) => theme
};
const utils = ShallowRenderer.createRenderer();
utils.render(<NavLinks {...landingPageProps} />);
@ -169,13 +170,13 @@ describe('<NavLinks />', () => {
user: {
isDonating: true,
username: 'moT01',
theme: 'default'
theme: Themes.Default
},
i18n: {
language: 'en'
},
t: t,
toggleNightMode: (theme: string) => theme
toggleNightMode: (theme: Themes) => theme
};
const utils = ShallowRenderer.createRenderer();
utils.render(<NavLinks {...landingPageProps} />);

View File

@ -1,5 +1,6 @@
import { render, screen } from '@testing-library/react';
import React from 'react';
import { Themes } from '../settings/theme';
import Profile from './profile';
@ -48,7 +49,7 @@ const userProps = {
points: 1,
sendQuincyEmail: true,
sound: true,
theme: 'string',
theme: Themes.Default,
twitter: 'string',
username: 'string',
website: 'string',

View File

@ -17,6 +17,7 @@ import {
} from '../../resources/cert-and-project-map';
import { maybeUrlRE } from '../../utils';
import { FlashMessages } from '../Flash/redux/flash-messages';
import ProjectModal from '../SolutionViewer/ProjectModal';
import { FullWidthRow, Spacer } from '../helpers';
@ -130,7 +131,7 @@ const isCertMapSelector = createSelector(
const honestyInfoMessage = {
type: 'info',
message: 'flash.honest-first'
message: FlashMessages.HonestFirst
};
const initialState = {

View File

@ -11,7 +11,7 @@ import { TFunction, withTranslation } from 'react-i18next';
import { FullWidthRow, Spacer } from '../helpers';
import BlockSaveButton from '../helpers/form/block-save-button';
import SoundSettings from './sound';
import ThemeSettings from './theme';
import ThemeSettings, { Themes } from './theme';
import UsernameSettings from './username';
type FormValues = {
@ -23,7 +23,7 @@ type FormValues = {
type AboutProps = {
about: string;
currentTheme: string;
currentTheme: Themes;
location: string;
name: string;
picture: string;
@ -31,7 +31,7 @@ type AboutProps = {
sound: boolean;
submitNewAbout: (formValues: FormValues) => void;
t: TFunction;
toggleNightMode: (theme: string) => void;
toggleNightMode: (theme: Themes) => void;
toggleSoundMode: (sound: boolean) => void;
username: string;
};

View File

@ -1,13 +1,17 @@
import { Form } from '@freecodecamp/react-bootstrap';
import React from 'react';
import { useTranslation } from 'react-i18next';
import store from 'store';
import ToggleSetting from './toggle-setting';
export enum Themes {
Night = 'night',
Default = 'default'
}
type ThemeProps = {
currentTheme: string;
toggleNightMode: (theme: 'default' | 'night') => void;
currentTheme: Themes;
toggleNightMode: (theme: Themes) => void;
};
export default function ThemeSettings({
@ -23,36 +27,14 @@ export default function ThemeSettings({
>
<ToggleSetting
action={t('settings.labels.night-mode')}
flag={currentTheme === 'night'}
flag={currentTheme === Themes.Night}
flagName='currentTheme'
offLabel={t('buttons.off')}
onLabel={t('buttons.on')}
toggleFlag={async () => {
const playSound = store.get('fcc-sound') as boolean | undefined;
if (playSound) {
const tone = await import('tone');
const nightToDayPlayer = new tone.Player(
'https://campfire-mode.freecodecamp.org/day.mp3'
).toDestination();
const dayToNightPlayer = new tone.Player(
'https://campfire-mode.freecodecamp.org/night.mp3'
).toDestination();
if (tone.context.state !== 'running') await tone.context.resume();
if (currentTheme === 'night') {
if (!nightToDayPlayer.loaded)
await nightToDayPlayer.load(
'https://campfire-mode.freecodecamp.org/day.mp3'
);
nightToDayPlayer.start();
} else {
if (!dayToNightPlayer.loaded)
await dayToNightPlayer.load(
'https://campfire-mode.freecodecamp.org/night.mp3'
);
dayToNightPlayer.start();
}
}
toggleNightMode(currentTheme === 'night' ? 'default' : 'night');
toggleFlag={() => {
toggleNightMode(
currentTheme === Themes.Night ? Themes.Default : Themes.Night
);
}}
/>
</Form>

View File

@ -1,6 +1,7 @@
import PropTypes from 'prop-types';
import { HandlerProps } from 'react-reflex';
import { SuperBlocks } from '../../../config/certification-settings';
import { Themes } from '../components/settings/theme';
import { certMap } from '../resources/cert-and-project-map';
export const UserPropType = PropTypes.shape({
@ -246,7 +247,7 @@ export type User = {
progressTimestamps: Array<unknown>;
sendQuincyEmail: boolean;
sound: boolean;
theme: string;
theme: Themes;
twitter: string;
username: string;
website: string;

View File

@ -3,6 +3,7 @@ import { call, put, takeEvery, take } 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 { actionTypes as appTypes } from '../action-types';
import { deleteAccountError, resetProgressError } from './';
@ -13,7 +14,7 @@ function* deleteAccountSaga() {
yield put(
createFlashMessage({
type: 'info',
message: 'flash.account-deleted'
message: FlashMessages.AccountDeleted
})
);
// remove current user information from application state
@ -30,7 +31,7 @@ function* resetProgressSaga() {
yield put(
createFlashMessage({
type: 'info',
message: 'flash.progress-reset'
message: FlashMessages.ProgressReset
})
);
// refresh current user data in application state

View File

@ -58,7 +58,9 @@ function* updateUserFlagSaga({ payload: update }) {
try {
const response = yield call(putUpdateUserFlag, update);
yield put(updateUserFlagComplete({ ...response, payload: update }));
yield put(createFlashMessage(response));
yield put(
createFlashMessage({ ...response, variables: { theme: update.theme } })
);
} catch (e) {
yield put(updateUserFlagError(e));
}

View File

@ -1,24 +1,25 @@
import { call, put, takeEvery } from 'redux-saga/effects';
import { createFlashMessage } from '../components/Flash/redux';
import { FlashMessages } from '../components/Flash/redux/flash-messages';
import { postWebhookToken, deleteWebhookToken } from '../utils/ajax';
import { postWebhookTokenComplete, deleteWebhookTokenComplete } from '.';
const message = {
created: {
type: 'success',
message: 'flash.token-created'
message: FlashMessages.TokenCreated
},
createErr: {
type: 'danger',
message: 'flash.create-token-err'
message: FlashMessages.CreateTokenErr
},
deleted: {
type: 'info',
message: 'flash.token-deleted'
message: FlashMessages.TokenDeleted
},
deleteErr: {
type: 'danger',
message: 'flash.delete-token-err'
message: FlashMessages.DeleteTokenErr
}
};

View File

@ -19,6 +19,7 @@ import { createSelector } from 'reselect';
import store from 'store';
import { Loader } from '../../../components/helpers';
import { Themes } from '../../../components/settings/theme';
import { userSelector, isDonationModalOpenSelector } from '../../../redux';
import {
ChallengeFiles,
@ -73,7 +74,7 @@ interface EditorProps {
submitChallenge: () => void;
stopResetting: () => void;
tests: Test[];
theme: string;
theme: Themes;
title: string;
updateFile: (object: {
fileKey: FileKey;
@ -111,7 +112,7 @@ const mapStateToProps = createSelector(
output: string[],
open,
isResetting: boolean,
{ theme = 'default' }: { theme: string },
{ theme = Themes.Default }: { theme: Themes },
tests: [{ text: string; testString: string }]
) => ({
canFocus: open ? false : canFocus,
@ -994,7 +995,7 @@ const Editor = (props: EditorProps): JSX.Element => {
}
const { theme } = props;
const editorTheme = theme === 'night' ? 'vs-dark-custom' : 'vs-custom';
const editorTheme = theme === Themes.Night ? 'vs-dark-custom' : 'vs-custom';
return (
<Suspense fallback={<Loader timeout={600} />}>
<span className='notranslate'>

View File

@ -13,8 +13,8 @@ import {
take,
cancel
} from 'redux-saga/effects';
import store from 'store';
import { playTone } from '../../../utils/tone';
import {
buildChallenge,
canBuildChallenge,
@ -99,17 +99,10 @@ export function* executeChallengeSaga({ payload }) {
yield put(updateTests(testResults));
const challengeComplete = testResults.every(test => test.pass && !test.err);
const playSound = store.get('fcc-sound');
let player;
if (playSound) {
void import('tone').then(tone => {
player = new tone.Player(
challengeComplete && payload?.showCompletionModal
? 'https://campfire-mode.freecodecamp.org/chal-comp.mp3'
: 'https://campfire-mode.freecodecamp.org/try-again.mp3'
).toDestination();
player.autostart = true;
});
if (challengeComplete) {
playTone('tests-completed');
} else {
playTone('tests-failed');
}
if (challengeComplete && payload?.showCompletionModal) {
yield put(openModal('completion'));

View File

@ -4,7 +4,6 @@ import { connect } from 'react-redux';
import ScrollableAnchor from 'react-scrollable-anchor';
import { bindActionCreators, Dispatch } from 'redux';
import { createSelector } from 'reselect';
import store from 'store';
import envData from '../../../../../config/env.json';
import { isAuditedCert } from '../../../../../utils/is-audited';
@ -14,6 +13,7 @@ import GreenPass from '../../../assets/icons/green-pass';
import { Link } from '../../../components/helpers';
import { completedChallengesSelector, executeGA } from '../../../redux';
import { ChallengeNode, CompletedChallenge } from '../../../redux/prop-types';
import { playTone } from '../../../utils/tone';
import { makeExpandedBlockSelector, toggleBlock } from '../redux';
import Challenges from './Challenges';
@ -61,20 +61,7 @@ export class Block extends Component<BlockProps> {
handleBlockClick(): void {
const { blockDashedName, toggleBlock, executeGA } = this.props;
const playSound = store.get('fcc-sound') as boolean;
if (playSound) {
void (async () => {
const tone = await import('tone');
const player = new tone.Player(
'https://tonejs.github.io/audio/berklee/guitar_chord1.mp3'
).toDestination();
if (tone.context.state !== 'running') {
void tone.context.resume();
}
player.autostart = playSound;
})();
}
void playTone('block-toggle');
executeGA({
type: 'event',
data: {

View File

@ -10,6 +10,7 @@ import {
SuperBlocks
} from '../../../../../config/certification-settings';
import { createFlashMessage } from '../../../components/Flash/redux';
import { FlashMessages } from '../../../components/Flash/redux/flash-messages';
import {
userFetchStateSelector,
stepsToClaimSelector,
@ -40,7 +41,7 @@ interface CertChallengeProps {
const honestyInfoMessage = {
type: 'info',
message: 'flash.honest-first'
message: FlashMessages.HonestFirst
};
const mapStateToProps = (state: unknown) => {

View File

@ -1,6 +1,8 @@
import { FlashMessages } from '../components/Flash/redux/flash-messages';
const certificateMissingErrorMessage = {
type: 'danger',
message: 'flash.certificate-missing'
message: FlashMessages.CertificateMissing
};
export default certificateMissingErrorMessage;

View File

@ -1,6 +1,8 @@
import { FlashMessages } from '../components/Flash/redux/flash-messages';
const reallyWeirdErrorMessage = {
type: 'danger',
message: 'flash.really-weird'
message: FlashMessages.ReallyWeird
};
export default reallyWeirdErrorMessage;

View File

@ -1,6 +1,8 @@
import { FlashMessages } from '../components/Flash/redux/flash-messages';
const reportedErrorMessage = {
type: 'danger',
message: 'flash.not-right'
message: FlashMessages.NotRight
};
export default reportedErrorMessage;

View File

@ -1,6 +1,8 @@
import { FlashMessages } from '../components/Flash/redux/flash-messages';
const standardErrorMessage = {
type: 'danger',
message: 'flash.went-wrong'
message: FlashMessages.WentWrong
};
export default standardErrorMessage;

View File

@ -0,0 +1,66 @@
import store from 'store';
import { FlashMessages } from '../../components/Flash/redux/flash-messages';
import { Themes } from '../../components/settings/theme';
const TRY_AGAIN = 'https://campfire-mode.freecodecamp.org/try-again.mp3';
const CHAL_COMP = 'https://campfire-mode.freecodecamp.org/chal-comp.mp3';
const toneUrls = {
[Themes.Default]: 'https://campfire-mode.freecodecamp.org/day.mp3',
[Themes.Night]: 'https://campfire-mode.freecodecamp.org/night.mp3',
donation: 'https://campfire-mode.freecodecamp.org/donate.mp3',
'tests-completed': CHAL_COMP,
'block-toggle': 'https://tonejs.github.io/audio/berklee/guitar_chord1.mp3',
'tests-failed': TRY_AGAIN,
completion: CHAL_COMP,
[FlashMessages.AccountDeleted]: TRY_AGAIN,
[FlashMessages.AddNameSuccess]: CHAL_COMP,
[FlashMessages.AlreadyClaimed]: TRY_AGAIN,
[FlashMessages.CertClaimSuccess]:
'https://campfire-mode.freecodecamp.org/cert.mp3',
[FlashMessages.CertificateMissing]: TRY_AGAIN,
[FlashMessages.CertsPrivate]: TRY_AGAIN,
[FlashMessages.CreateTokenErr]: TRY_AGAIN,
[FlashMessages.DeleteTokenErr]: TRY_AGAIN,
[FlashMessages.EmailValid]: CHAL_COMP,
[FlashMessages.HonestFirst]: TRY_AGAIN,
[FlashMessages.IncompleteSteps]: TRY_AGAIN,
[FlashMessages.NameNeeded]: TRY_AGAIN,
// [FlashMessages.None]: '',
[FlashMessages.NotEligible]: TRY_AGAIN,
[FlashMessages.NotHonest]: TRY_AGAIN,
[FlashMessages.NotRight]: TRY_AGAIN,
[FlashMessages.ProfilePrivate]: TRY_AGAIN,
[FlashMessages.ProgressReset]: TRY_AGAIN,
[FlashMessages.ProvideUsername]: TRY_AGAIN,
[FlashMessages.ReallyWeird]: TRY_AGAIN,
[FlashMessages.ReportSent]: CHAL_COMP,
[FlashMessages.SigninSuccess]: CHAL_COMP,
[FlashMessages.TokenCreated]: CHAL_COMP,
[FlashMessages.TokenDeleted]: CHAL_COMP,
[FlashMessages.UpdatedPreferences]: CHAL_COMP,
[FlashMessages.UsernameNotFound]: TRY_AGAIN,
[FlashMessages.UsernameTaken]: TRY_AGAIN,
[FlashMessages.UsernameUpdated]: CHAL_COMP,
[FlashMessages.UsernameUsed]: TRY_AGAIN,
[FlashMessages.UserNotCertified]: TRY_AGAIN,
[FlashMessages.WrongName]: TRY_AGAIN,
[FlashMessages.WrongUpdating]: TRY_AGAIN,
[FlashMessages.WentWrong]: TRY_AGAIN
} as const;
type ToneStates = keyof typeof toneUrls;
export async function playTone(state: ToneStates): Promise<void> {
const playSound = !!store.get('fcc-sound');
if (playSound && toneUrls[state]) {
const tone = await import('tone');
if (tone.context.state !== 'running') {
tone.context.resume().catch(err => {
console.error('Error resuming audio context', err);
});
}
const player = new tone.Player(toneUrls[state]).toDestination();
player.autostart = true;
}
}