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:
@ -14,6 +14,7 @@ import DonateForm from '../components/Donation/donate-form';
|
|||||||
import { createFlashMessage } from '../components/Flash/redux';
|
import { createFlashMessage } from '../components/Flash/redux';
|
||||||
import { Loader, Spacer } from '../components/helpers';
|
import { Loader, Spacer } from '../components/helpers';
|
||||||
import RedirectHome from '../components/redirect-home';
|
import RedirectHome from '../components/redirect-home';
|
||||||
|
import { Themes } from '../components/settings/theme';
|
||||||
import {
|
import {
|
||||||
showCertSelector,
|
showCertSelector,
|
||||||
showCertFetchStateSelector,
|
showCertFetchStateSelector,
|
||||||
@ -268,7 +269,7 @@ const ShowCertification = (props: ShowCertificationProps): JSX.Element => {
|
|||||||
<Row>
|
<Row>
|
||||||
<Col lg={8} lgOffset={2} sm={10} smOffset={1} xs={12}>
|
<Col lg={8} lgOffset={2} sm={10} smOffset={1} xs={12}>
|
||||||
<DonateForm
|
<DonateForm
|
||||||
defaultTheme='default'
|
defaultTheme={Themes.Default}
|
||||||
handleProcessing={handleProcessing}
|
handleProcessing={handleProcessing}
|
||||||
isMinimalForm={true}
|
isMinimalForm={true}
|
||||||
/>
|
/>
|
||||||
|
@ -16,6 +16,7 @@ import Honesty from '../components/settings/honesty';
|
|||||||
import Internet from '../components/settings/internet';
|
import Internet from '../components/settings/internet';
|
||||||
import Portfolio from '../components/settings/portfolio';
|
import Portfolio from '../components/settings/portfolio';
|
||||||
import Privacy from '../components/settings/privacy';
|
import Privacy from '../components/settings/privacy';
|
||||||
|
import { Themes } from '../components/settings/theme';
|
||||||
import WebhookToken from '../components/settings/webhook-token';
|
import WebhookToken from '../components/settings/webhook-token';
|
||||||
import {
|
import {
|
||||||
signInLoadingSelector,
|
signInLoadingSelector,
|
||||||
@ -35,7 +36,7 @@ interface ShowSettingsProps {
|
|||||||
navigate: (location: string) => void;
|
navigate: (location: string) => void;
|
||||||
showLoading: boolean;
|
showLoading: boolean;
|
||||||
submitNewAbout: () => void;
|
submitNewAbout: () => void;
|
||||||
toggleNightMode: (theme: string) => void;
|
toggleNightMode: (theme: Themes) => void;
|
||||||
toggleSoundMode: (sound: boolean) => void;
|
toggleSoundMode: (sound: boolean) => void;
|
||||||
updateInternetSettings: () => void;
|
updateInternetSettings: () => void;
|
||||||
updateIsHonest: () => void;
|
updateIsHonest: () => void;
|
||||||
@ -61,7 +62,7 @@ const mapDispatchToProps = {
|
|||||||
createFlashMessage,
|
createFlashMessage,
|
||||||
navigate,
|
navigate,
|
||||||
submitNewAbout,
|
submitNewAbout,
|
||||||
toggleNightMode: (theme: string) => updateUserFlag({ theme }),
|
toggleNightMode: (theme: Themes) => updateUserFlag({ theme }),
|
||||||
toggleSoundMode: (sound: boolean) => updateUserFlag({ sound }),
|
toggleSoundMode: (sound: boolean) => updateUserFlag({ sound }),
|
||||||
updateInternetSettings: updateUserFlag,
|
updateInternetSettings: updateUserFlag,
|
||||||
updateIsHonest: updateUserFlag,
|
updateIsHonest: updateUserFlag,
|
||||||
|
@ -26,6 +26,7 @@ import {
|
|||||||
postChargeStripeCard
|
postChargeStripeCard
|
||||||
} from '../../redux';
|
} from '../../redux';
|
||||||
import Spacer from '../helpers/spacer';
|
import Spacer from '../helpers/spacer';
|
||||||
|
import { Themes } from '../settings/theme';
|
||||||
import DonateCompletion from './donate-completion';
|
import DonateCompletion from './donate-completion';
|
||||||
import PatreonButton from './patreon-button';
|
import PatreonButton from './patreon-button';
|
||||||
import type { AddDonationData } from './paypal-button';
|
import type { AddDonationData } from './paypal-button';
|
||||||
@ -63,7 +64,7 @@ type DonateFormProps = {
|
|||||||
duration: string;
|
duration: string;
|
||||||
handleAuthentication: HandleAuthentication;
|
handleAuthentication: HandleAuthentication;
|
||||||
}) => void;
|
}) => void;
|
||||||
defaultTheme?: string;
|
defaultTheme?: Themes;
|
||||||
email: string;
|
email: string;
|
||||||
handleProcessing: (duration: string, amount: number, action: string) => void;
|
handleProcessing: (duration: string, amount: number, action: string) => void;
|
||||||
donationFormState: DonateFormState;
|
donationFormState: DonateFormState;
|
||||||
@ -74,7 +75,7 @@ type DonateFormProps = {
|
|||||||
label: string,
|
label: string,
|
||||||
{ usd, hours }?: { usd?: string | number; hours?: string }
|
{ usd, hours }?: { usd?: string | number; hours?: string }
|
||||||
) => string;
|
) => string;
|
||||||
theme: string;
|
theme: Themes;
|
||||||
updateDonationFormState: (state: AddDonationData) => unknown;
|
updateDonationFormState: (state: AddDonationData) => unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -87,7 +88,7 @@ const mapStateToProps = createSelector(
|
|||||||
showLoading: DonateFormProps['showLoading'],
|
showLoading: DonateFormProps['showLoading'],
|
||||||
isSignedIn: DonateFormProps['isSignedIn'],
|
isSignedIn: DonateFormProps['isSignedIn'],
|
||||||
donationFormState: DonateFormState,
|
donationFormState: DonateFormState,
|
||||||
{ email, theme }: { email: string; theme: string }
|
{ email, theme }: { email: string; theme: Themes }
|
||||||
) => ({
|
) => ({
|
||||||
isSignedIn,
|
isSignedIn,
|
||||||
showLoading,
|
showLoading,
|
||||||
|
@ -6,7 +6,6 @@ import { connect } from 'react-redux';
|
|||||||
import { goToAnchor } from 'react-scrollable-anchor';
|
import { goToAnchor } from 'react-scrollable-anchor';
|
||||||
import { bindActionCreators, Dispatch, AnyAction } from 'redux';
|
import { bindActionCreators, Dispatch, AnyAction } from 'redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import store from 'store';
|
|
||||||
import { modalDefaultDonation } from '../../../../config/donation-settings';
|
import { modalDefaultDonation } from '../../../../config/donation-settings';
|
||||||
import Cup from '../../assets/icons/cup';
|
import Cup from '../../assets/icons/cup';
|
||||||
import Heart from '../../assets/icons/heart';
|
import Heart from '../../assets/icons/heart';
|
||||||
@ -18,6 +17,7 @@ import {
|
|||||||
executeGA
|
executeGA
|
||||||
} from '../../redux';
|
} from '../../redux';
|
||||||
import { isLocationSuperBlock } from '../../utils/path-parsers';
|
import { isLocationSuperBlock } from '../../utils/path-parsers';
|
||||||
|
import { playTone } from '../../utils/tone';
|
||||||
import { Spacer } from '../helpers';
|
import { Spacer } from '../helpers';
|
||||||
import DonateForm from './donate-form';
|
import DonateForm from './donate-form';
|
||||||
|
|
||||||
@ -76,16 +76,7 @@ function DonateModal({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (show) {
|
if (show) {
|
||||||
const playSound = store.get('fcc-sound') as boolean | undefined;
|
void playTone('donation');
|
||||||
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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
executeGA({ type: 'modal', data: '/donation-modal' });
|
executeGA({ type: 'modal', data: '/donation-modal' });
|
||||||
executeGA({
|
executeGA({
|
||||||
type: 'event',
|
type: 'event',
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { render } from '@testing-library/react';
|
import { render } from '@testing-library/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Themes } from '../settings/theme';
|
||||||
|
|
||||||
import { PaypalButton } from './paypal-button';
|
import { PaypalButton } from './paypal-button';
|
||||||
|
|
||||||
@ -11,7 +12,7 @@ const commonProps = {
|
|||||||
onDonationStateChange: () => null,
|
onDonationStateChange: () => null,
|
||||||
isPaypalLoading: true,
|
isPaypalLoading: true,
|
||||||
t: jest.fn(),
|
t: jest.fn(),
|
||||||
theme: 'night',
|
theme: Themes.Night,
|
||||||
handlePaymentButtonLoad: jest.fn(),
|
handlePaymentButtonLoad: jest.fn(),
|
||||||
isMinimalForm: true
|
isMinimalForm: true
|
||||||
};
|
};
|
||||||
|
@ -13,6 +13,7 @@ import {
|
|||||||
} from '../../../../config/donation-settings';
|
} from '../../../../config/donation-settings';
|
||||||
import envData from '../../../../config/env.json';
|
import envData from '../../../../config/env.json';
|
||||||
import { signInLoadingSelector, userSelector } from '../../redux';
|
import { signInLoadingSelector, userSelector } from '../../redux';
|
||||||
|
import { Themes } from '../settings/theme';
|
||||||
import PayPalButtonScriptLoader from './paypal-button-script-loader';
|
import PayPalButtonScriptLoader from './paypal-button-script-loader';
|
||||||
|
|
||||||
type PaypalButtonProps = {
|
type PaypalButtonProps = {
|
||||||
@ -41,7 +42,7 @@ type PaypalButtonProps = {
|
|||||||
skipAddDonation?: boolean;
|
skipAddDonation?: boolean;
|
||||||
t: (label: string) => string;
|
t: (label: string) => string;
|
||||||
ref?: Ref<PaypalButton>;
|
ref?: Ref<PaypalButton>;
|
||||||
theme: string;
|
theme: Themes;
|
||||||
isSubscription?: boolean;
|
isSubscription?: boolean;
|
||||||
handlePaymentButtonLoad: (provider: 'stripe' | 'paypal') => void;
|
handlePaymentButtonLoad: (provider: 'stripe' | 'paypal') => void;
|
||||||
isMinimalForm: boolean | undefined;
|
isMinimalForm: boolean | undefined;
|
||||||
@ -131,7 +132,7 @@ export class PaypalButton extends Component<
|
|||||||
const { duration, planId, amount } = this.state;
|
const { duration, planId, amount } = this.state;
|
||||||
const { t, theme, isPaypalLoading, isMinimalForm } = this.props;
|
const { t, theme, isPaypalLoading, isMinimalForm } = this.props;
|
||||||
const isSubscription = duration !== 'onetime';
|
const isSubscription = duration !== 'onetime';
|
||||||
const buttonColor = theme === 'night' ? 'white' : 'gold';
|
const buttonColor = theme === Themes.Night ? 'white' : 'gold';
|
||||||
if (!paypalClientId) {
|
if (!paypalClientId) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ import type {
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
import envData from '../../../../config/env.json';
|
import envData from '../../../../config/env.json';
|
||||||
|
import { Themes } from '../settings/theme';
|
||||||
import { AddDonationData } from './paypal-button';
|
import { AddDonationData } from './paypal-button';
|
||||||
|
|
||||||
const { stripePublicKey }: { stripePublicKey: string | null } = envData;
|
const { stripePublicKey }: { stripePublicKey: string | null } = envData;
|
||||||
@ -32,7 +33,7 @@ interface FormPropTypes {
|
|||||||
handleAuthentication: HandleAuthentication
|
handleAuthentication: HandleAuthentication
|
||||||
) => void;
|
) => void;
|
||||||
t: (label: string) => string;
|
t: (label: string) => string;
|
||||||
theme: string;
|
theme: Themes;
|
||||||
processing: boolean;
|
processing: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,7 +93,7 @@ const StripeCardForm = ({
|
|||||||
style: {
|
style: {
|
||||||
base: {
|
base: {
|
||||||
fontSize: '18px',
|
fontSize: '18px',
|
||||||
color: `${theme === 'night' ? '#fff' : '#0a0a23'}`,
|
color: `${theme === Themes.Night ? '#fff' : '#0a0a23'}`,
|
||||||
'::placeholder': {
|
'::placeholder': {
|
||||||
color: `#858591`
|
color: `#858591`
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import { Stripe, loadStripe } from '@stripe/stripe-js';
|
|||||||
import type { Token, PaymentRequest } from '@stripe/stripe-js';
|
import type { Token, PaymentRequest } from '@stripe/stripe-js';
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import envData from '../../../../config/env.json';
|
import envData from '../../../../config/env.json';
|
||||||
|
import { Themes } from '../settings/theme';
|
||||||
import { AddDonationData } from './paypal-button';
|
import { AddDonationData } from './paypal-button';
|
||||||
|
|
||||||
const { stripePublicKey }: { stripePublicKey: string | null } = envData;
|
const { stripePublicKey }: { stripePublicKey: string | null } = envData;
|
||||||
@ -14,7 +15,7 @@ const { stripePublicKey }: { stripePublicKey: string | null } = envData;
|
|||||||
interface WrapperProps {
|
interface WrapperProps {
|
||||||
label: string;
|
label: string;
|
||||||
amount: number;
|
amount: number;
|
||||||
theme: string;
|
theme: Themes;
|
||||||
postStripeDonation: (
|
postStripeDonation: (
|
||||||
token: Token,
|
token: Token,
|
||||||
payerEmail: string | undefined,
|
payerEmail: string | undefined,
|
||||||
@ -98,7 +99,7 @@ const WalletsButton = ({
|
|||||||
style: {
|
style: {
|
||||||
paymentRequestButton: {
|
paymentRequestButton: {
|
||||||
type: 'default',
|
type: 'default',
|
||||||
theme: theme === 'night' ? 'light' : 'dark',
|
theme: theme === Themes.Night ? 'light' : 'dark',
|
||||||
height: '43px'
|
height: '43px'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
35
client/src/components/Flash/redux/flash-messages.ts
Normal file
35
client/src/components/Flash/redux/flash-messages.ts
Normal 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'
|
||||||
|
}
|
@ -1,17 +1,12 @@
|
|||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import store from 'store';
|
|
||||||
import { FlashState, State } from '../../../redux/types';
|
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';
|
export const FlashApp = 'flash';
|
||||||
|
|
||||||
const initialState = {
|
|
||||||
message: {
|
|
||||||
id: '',
|
|
||||||
type: '',
|
|
||||||
message: ''
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const sagas = [];
|
export const sagas = [];
|
||||||
|
|
||||||
export const flashMessageSelector = (state: State): FlashState['message'] =>
|
export const flashMessageSelector = (state: State): FlashState['message'] =>
|
||||||
@ -26,32 +21,26 @@ enum FlashActionTypes {
|
|||||||
|
|
||||||
export type FlashMessageArg = {
|
export type FlashMessageArg = {
|
||||||
type: string;
|
type: string;
|
||||||
message: string;
|
message: FlashMessages;
|
||||||
variables?: Record<string, unknown>;
|
variables?: Record<string, unknown>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
message: {
|
||||||
|
id: '',
|
||||||
|
type: '',
|
||||||
|
message: FlashMessages.None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const createFlashMessage = (
|
export const createFlashMessage = (
|
||||||
flash: FlashMessageArg
|
flash: FlashMessageArg
|
||||||
): ReducerPayload<FlashActionTypes.CreateFlashMessage> => {
|
): ReducerPayload<FlashActionTypes.CreateFlashMessage> => {
|
||||||
const playSound = store.get('fcc-sound') as boolean | undefined;
|
// Nightmode theme has special tones
|
||||||
if (playSound) {
|
if (flash.variables?.theme) {
|
||||||
void import('tone').then(tone => {
|
void playTone(flash.variables.theme as Themes);
|
||||||
if (tone.context.state !== 'running') {
|
} else if (flash.message !== FlashMessages.None) {
|
||||||
void tone.context.resume();
|
void playTone(flash.message);
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
type: FlashActionTypes.CreateFlashMessage,
|
type: FlashActionTypes.CreateFlashMessage,
|
||||||
|
@ -28,6 +28,7 @@ import { hardGoTo as navigate } from '../../../redux';
|
|||||||
import { updateUserFlag } from '../../../redux/settings';
|
import { updateUserFlag } from '../../../redux/settings';
|
||||||
import createLanguageRedirect from '../../create-language-redirect';
|
import createLanguageRedirect from '../../create-language-redirect';
|
||||||
import { Link } from '../../helpers';
|
import { Link } from '../../helpers';
|
||||||
|
import { Themes } from '../../settings/theme';
|
||||||
|
|
||||||
const { clientLocale, radioLocation, apiLocation } = envData;
|
const { clientLocale, radioLocation, apiLocation } = envData;
|
||||||
|
|
||||||
@ -46,7 +47,7 @@ export interface NavLinksProps {
|
|||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
navigate,
|
navigate,
|
||||||
toggleNightMode: (theme: unknown) => updateUserFlag({ theme })
|
toggleNightMode: (theme: Themes) => updateUserFlag({ theme })
|
||||||
};
|
};
|
||||||
|
|
||||||
export class NavLinks extends Component<NavLinksProps, {}> {
|
export class NavLinks extends Component<NavLinksProps, {}> {
|
||||||
@ -57,8 +58,10 @@ export class NavLinks extends Component<NavLinksProps, {}> {
|
|||||||
this.handleLanguageChange = this.handleLanguageChange.bind(this);
|
this.handleLanguageChange = this.handleLanguageChange.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleTheme(currentTheme = 'default', toggleNightMode: any) {
|
toggleTheme(currentTheme = Themes.Default, toggleNightMode: any) {
|
||||||
toggleNightMode(currentTheme === 'night' ? 'default' : 'night');
|
toggleNightMode(
|
||||||
|
currentTheme === Themes.Night ? Themes.Default : Themes.Night
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLanguageChange = (
|
handleLanguageChange = (
|
||||||
@ -175,7 +178,7 @@ export class NavLinks extends Component<NavLinksProps, {}> {
|
|||||||
{username ? (
|
{username ? (
|
||||||
<>
|
<>
|
||||||
<span>{t('settings.labels.night-mode')}</span>
|
<span>{t('settings.labels.night-mode')}</span>
|
||||||
{theme === 'night' ? (
|
{theme === Themes.Night ? (
|
||||||
<FontAwesomeIcon icon={faCheckSquare} />
|
<FontAwesomeIcon icon={faCheckSquare} />
|
||||||
) : (
|
) : (
|
||||||
<FontAwesomeIcon icon={faSquare} />
|
<FontAwesomeIcon icon={faSquare} />
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
availableLangs,
|
availableLangs,
|
||||||
langDisplayNames
|
langDisplayNames
|
||||||
} from '../../../../config/i18n/all-langs';
|
} from '../../../../config/i18n/all-langs';
|
||||||
|
import { Themes } from '../settings/theme';
|
||||||
import AuthOrProfile from './components/auth-or-profile';
|
import AuthOrProfile from './components/auth-or-profile';
|
||||||
import { NavLinks } from './components/nav-links';
|
import { NavLinks } from './components/nav-links';
|
||||||
import { UniversalNav } from './components/universal-nav';
|
import { UniversalNav } from './components/universal-nav';
|
||||||
@ -50,12 +51,12 @@ describe('<NavLinks />', () => {
|
|||||||
user: {
|
user: {
|
||||||
isDonating: false,
|
isDonating: false,
|
||||||
username: null,
|
username: null,
|
||||||
theme: 'default'
|
theme: Themes.Default
|
||||||
},
|
},
|
||||||
i18n: {
|
i18n: {
|
||||||
language: 'en'
|
language: 'en'
|
||||||
},
|
},
|
||||||
toggleNightMode: (theme: string) => theme,
|
toggleNightMode: (theme: Themes) => theme,
|
||||||
t: t
|
t: t
|
||||||
};
|
};
|
||||||
const utils = ShallowRenderer.createRenderer();
|
const utils = ShallowRenderer.createRenderer();
|
||||||
@ -81,13 +82,13 @@ describe('<NavLinks />', () => {
|
|||||||
user: {
|
user: {
|
||||||
isDonating: false,
|
isDonating: false,
|
||||||
username: 'nhcarrigan',
|
username: 'nhcarrigan',
|
||||||
theme: 'default'
|
theme: Themes.Default
|
||||||
},
|
},
|
||||||
i18n: {
|
i18n: {
|
||||||
language: 'en'
|
language: 'en'
|
||||||
},
|
},
|
||||||
t: t,
|
t: t,
|
||||||
toggleNightMode: (theme: string) => theme
|
toggleNightMode: (theme: Themes) => theme
|
||||||
};
|
};
|
||||||
const utils = ShallowRenderer.createRenderer();
|
const utils = ShallowRenderer.createRenderer();
|
||||||
utils.render(<NavLinks {...landingPageProps} />);
|
utils.render(<NavLinks {...landingPageProps} />);
|
||||||
@ -111,13 +112,13 @@ describe('<NavLinks />', () => {
|
|||||||
user: {
|
user: {
|
||||||
isDonating: true,
|
isDonating: true,
|
||||||
username: 'moT01',
|
username: 'moT01',
|
||||||
theme: 'default'
|
theme: Themes.Default
|
||||||
},
|
},
|
||||||
i18n: {
|
i18n: {
|
||||||
language: 'en'
|
language: 'en'
|
||||||
},
|
},
|
||||||
t: t,
|
t: t,
|
||||||
toggleNightMode: (theme: string) => theme
|
toggleNightMode: (theme: Themes) => theme
|
||||||
};
|
};
|
||||||
const utils = ShallowRenderer.createRenderer();
|
const utils = ShallowRenderer.createRenderer();
|
||||||
utils.render(<NavLinks {...landingPageProps} />);
|
utils.render(<NavLinks {...landingPageProps} />);
|
||||||
@ -143,13 +144,13 @@ describe('<NavLinks />', () => {
|
|||||||
user: {
|
user: {
|
||||||
isDonating: true,
|
isDonating: true,
|
||||||
username: 'moT01',
|
username: 'moT01',
|
||||||
theme: 'default'
|
theme: Themes.Default
|
||||||
},
|
},
|
||||||
i18n: {
|
i18n: {
|
||||||
language: 'en'
|
language: 'en'
|
||||||
},
|
},
|
||||||
t: t,
|
t: t,
|
||||||
toggleNightMode: (theme: string) => theme
|
toggleNightMode: (theme: Themes) => theme
|
||||||
};
|
};
|
||||||
const utils = ShallowRenderer.createRenderer();
|
const utils = ShallowRenderer.createRenderer();
|
||||||
utils.render(<NavLinks {...landingPageProps} />);
|
utils.render(<NavLinks {...landingPageProps} />);
|
||||||
@ -169,13 +170,13 @@ describe('<NavLinks />', () => {
|
|||||||
user: {
|
user: {
|
||||||
isDonating: true,
|
isDonating: true,
|
||||||
username: 'moT01',
|
username: 'moT01',
|
||||||
theme: 'default'
|
theme: Themes.Default
|
||||||
},
|
},
|
||||||
i18n: {
|
i18n: {
|
||||||
language: 'en'
|
language: 'en'
|
||||||
},
|
},
|
||||||
t: t,
|
t: t,
|
||||||
toggleNightMode: (theme: string) => theme
|
toggleNightMode: (theme: Themes) => theme
|
||||||
};
|
};
|
||||||
const utils = ShallowRenderer.createRenderer();
|
const utils = ShallowRenderer.createRenderer();
|
||||||
utils.render(<NavLinks {...landingPageProps} />);
|
utils.render(<NavLinks {...landingPageProps} />);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Themes } from '../settings/theme';
|
||||||
|
|
||||||
import Profile from './profile';
|
import Profile from './profile';
|
||||||
|
|
||||||
@ -48,7 +49,7 @@ const userProps = {
|
|||||||
points: 1,
|
points: 1,
|
||||||
sendQuincyEmail: true,
|
sendQuincyEmail: true,
|
||||||
sound: true,
|
sound: true,
|
||||||
theme: 'string',
|
theme: Themes.Default,
|
||||||
twitter: 'string',
|
twitter: 'string',
|
||||||
username: 'string',
|
username: 'string',
|
||||||
website: 'string',
|
website: 'string',
|
||||||
|
@ -17,6 +17,7 @@ import {
|
|||||||
} from '../../resources/cert-and-project-map';
|
} from '../../resources/cert-and-project-map';
|
||||||
|
|
||||||
import { maybeUrlRE } from '../../utils';
|
import { maybeUrlRE } from '../../utils';
|
||||||
|
import { FlashMessages } from '../Flash/redux/flash-messages';
|
||||||
import ProjectModal from '../SolutionViewer/ProjectModal';
|
import ProjectModal from '../SolutionViewer/ProjectModal';
|
||||||
import { FullWidthRow, Spacer } from '../helpers';
|
import { FullWidthRow, Spacer } from '../helpers';
|
||||||
|
|
||||||
@ -130,7 +131,7 @@ const isCertMapSelector = createSelector(
|
|||||||
|
|
||||||
const honestyInfoMessage = {
|
const honestyInfoMessage = {
|
||||||
type: 'info',
|
type: 'info',
|
||||||
message: 'flash.honest-first'
|
message: FlashMessages.HonestFirst
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
|
@ -11,7 +11,7 @@ import { TFunction, withTranslation } from 'react-i18next';
|
|||||||
import { FullWidthRow, Spacer } from '../helpers';
|
import { FullWidthRow, Spacer } from '../helpers';
|
||||||
import BlockSaveButton from '../helpers/form/block-save-button';
|
import BlockSaveButton from '../helpers/form/block-save-button';
|
||||||
import SoundSettings from './sound';
|
import SoundSettings from './sound';
|
||||||
import ThemeSettings from './theme';
|
import ThemeSettings, { Themes } from './theme';
|
||||||
import UsernameSettings from './username';
|
import UsernameSettings from './username';
|
||||||
|
|
||||||
type FormValues = {
|
type FormValues = {
|
||||||
@ -23,7 +23,7 @@ type FormValues = {
|
|||||||
|
|
||||||
type AboutProps = {
|
type AboutProps = {
|
||||||
about: string;
|
about: string;
|
||||||
currentTheme: string;
|
currentTheme: Themes;
|
||||||
location: string;
|
location: string;
|
||||||
name: string;
|
name: string;
|
||||||
picture: string;
|
picture: string;
|
||||||
@ -31,7 +31,7 @@ type AboutProps = {
|
|||||||
sound: boolean;
|
sound: boolean;
|
||||||
submitNewAbout: (formValues: FormValues) => void;
|
submitNewAbout: (formValues: FormValues) => void;
|
||||||
t: TFunction;
|
t: TFunction;
|
||||||
toggleNightMode: (theme: string) => void;
|
toggleNightMode: (theme: Themes) => void;
|
||||||
toggleSoundMode: (sound: boolean) => void;
|
toggleSoundMode: (sound: boolean) => void;
|
||||||
username: string;
|
username: string;
|
||||||
};
|
};
|
||||||
|
@ -1,13 +1,17 @@
|
|||||||
import { Form } from '@freecodecamp/react-bootstrap';
|
import { Form } from '@freecodecamp/react-bootstrap';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import store from 'store';
|
|
||||||
|
|
||||||
import ToggleSetting from './toggle-setting';
|
import ToggleSetting from './toggle-setting';
|
||||||
|
|
||||||
|
export enum Themes {
|
||||||
|
Night = 'night',
|
||||||
|
Default = 'default'
|
||||||
|
}
|
||||||
|
|
||||||
type ThemeProps = {
|
type ThemeProps = {
|
||||||
currentTheme: string;
|
currentTheme: Themes;
|
||||||
toggleNightMode: (theme: 'default' | 'night') => void;
|
toggleNightMode: (theme: Themes) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ThemeSettings({
|
export default function ThemeSettings({
|
||||||
@ -23,36 +27,14 @@ export default function ThemeSettings({
|
|||||||
>
|
>
|
||||||
<ToggleSetting
|
<ToggleSetting
|
||||||
action={t('settings.labels.night-mode')}
|
action={t('settings.labels.night-mode')}
|
||||||
flag={currentTheme === 'night'}
|
flag={currentTheme === Themes.Night}
|
||||||
flagName='currentTheme'
|
flagName='currentTheme'
|
||||||
offLabel={t('buttons.off')}
|
offLabel={t('buttons.off')}
|
||||||
onLabel={t('buttons.on')}
|
onLabel={t('buttons.on')}
|
||||||
toggleFlag={async () => {
|
toggleFlag={() => {
|
||||||
const playSound = store.get('fcc-sound') as boolean | undefined;
|
toggleNightMode(
|
||||||
if (playSound) {
|
currentTheme === Themes.Night ? Themes.Default : Themes.Night
|
||||||
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');
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { HandlerProps } from 'react-reflex';
|
import { HandlerProps } from 'react-reflex';
|
||||||
import { SuperBlocks } from '../../../config/certification-settings';
|
import { SuperBlocks } from '../../../config/certification-settings';
|
||||||
|
import { Themes } from '../components/settings/theme';
|
||||||
import { certMap } from '../resources/cert-and-project-map';
|
import { certMap } from '../resources/cert-and-project-map';
|
||||||
|
|
||||||
export const UserPropType = PropTypes.shape({
|
export const UserPropType = PropTypes.shape({
|
||||||
@ -246,7 +247,7 @@ export type User = {
|
|||||||
progressTimestamps: Array<unknown>;
|
progressTimestamps: Array<unknown>;
|
||||||
sendQuincyEmail: boolean;
|
sendQuincyEmail: boolean;
|
||||||
sound: boolean;
|
sound: boolean;
|
||||||
theme: string;
|
theme: Themes;
|
||||||
twitter: string;
|
twitter: string;
|
||||||
username: string;
|
username: string;
|
||||||
website: string;
|
website: string;
|
||||||
|
@ -3,6 +3,7 @@ import { call, put, takeEvery, take } from 'redux-saga/effects';
|
|||||||
|
|
||||||
import { resetUserData, fetchUser } from '../';
|
import { resetUserData, fetchUser } from '../';
|
||||||
import { createFlashMessage } from '../../components/Flash/redux';
|
import { createFlashMessage } from '../../components/Flash/redux';
|
||||||
|
import { FlashMessages } from '../../components/Flash/redux/flash-messages';
|
||||||
import { postResetProgress, postDeleteAccount } from '../../utils/ajax';
|
import { postResetProgress, postDeleteAccount } from '../../utils/ajax';
|
||||||
import { actionTypes as appTypes } from '../action-types';
|
import { actionTypes as appTypes } from '../action-types';
|
||||||
import { deleteAccountError, resetProgressError } from './';
|
import { deleteAccountError, resetProgressError } from './';
|
||||||
@ -13,7 +14,7 @@ function* deleteAccountSaga() {
|
|||||||
yield put(
|
yield put(
|
||||||
createFlashMessage({
|
createFlashMessage({
|
||||||
type: 'info',
|
type: 'info',
|
||||||
message: 'flash.account-deleted'
|
message: FlashMessages.AccountDeleted
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
// remove current user information from application state
|
// remove current user information from application state
|
||||||
@ -30,7 +31,7 @@ function* resetProgressSaga() {
|
|||||||
yield put(
|
yield put(
|
||||||
createFlashMessage({
|
createFlashMessage({
|
||||||
type: 'info',
|
type: 'info',
|
||||||
message: 'flash.progress-reset'
|
message: FlashMessages.ProgressReset
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
// refresh current user data in application state
|
// refresh current user data in application state
|
||||||
|
@ -58,7 +58,9 @@ function* updateUserFlagSaga({ payload: update }) {
|
|||||||
try {
|
try {
|
||||||
const response = yield call(putUpdateUserFlag, update);
|
const response = yield call(putUpdateUserFlag, update);
|
||||||
yield put(updateUserFlagComplete({ ...response, payload: update }));
|
yield put(updateUserFlagComplete({ ...response, payload: update }));
|
||||||
yield put(createFlashMessage(response));
|
yield put(
|
||||||
|
createFlashMessage({ ...response, variables: { theme: update.theme } })
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
yield put(updateUserFlagError(e));
|
yield put(updateUserFlagError(e));
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,25 @@
|
|||||||
import { call, put, takeEvery } from 'redux-saga/effects';
|
import { call, put, takeEvery } from 'redux-saga/effects';
|
||||||
import { createFlashMessage } from '../components/Flash/redux';
|
import { createFlashMessage } from '../components/Flash/redux';
|
||||||
|
import { FlashMessages } from '../components/Flash/redux/flash-messages';
|
||||||
import { postWebhookToken, deleteWebhookToken } from '../utils/ajax';
|
import { postWebhookToken, deleteWebhookToken } from '../utils/ajax';
|
||||||
import { postWebhookTokenComplete, deleteWebhookTokenComplete } from '.';
|
import { postWebhookTokenComplete, deleteWebhookTokenComplete } from '.';
|
||||||
|
|
||||||
const message = {
|
const message = {
|
||||||
created: {
|
created: {
|
||||||
type: 'success',
|
type: 'success',
|
||||||
message: 'flash.token-created'
|
message: FlashMessages.TokenCreated
|
||||||
},
|
},
|
||||||
createErr: {
|
createErr: {
|
||||||
type: 'danger',
|
type: 'danger',
|
||||||
message: 'flash.create-token-err'
|
message: FlashMessages.CreateTokenErr
|
||||||
},
|
},
|
||||||
deleted: {
|
deleted: {
|
||||||
type: 'info',
|
type: 'info',
|
||||||
message: 'flash.token-deleted'
|
message: FlashMessages.TokenDeleted
|
||||||
},
|
},
|
||||||
deleteErr: {
|
deleteErr: {
|
||||||
type: 'danger',
|
type: 'danger',
|
||||||
message: 'flash.delete-token-err'
|
message: FlashMessages.DeleteTokenErr
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ import { createSelector } from 'reselect';
|
|||||||
import store from 'store';
|
import store from 'store';
|
||||||
|
|
||||||
import { Loader } from '../../../components/helpers';
|
import { Loader } from '../../../components/helpers';
|
||||||
|
import { Themes } from '../../../components/settings/theme';
|
||||||
import { userSelector, isDonationModalOpenSelector } from '../../../redux';
|
import { userSelector, isDonationModalOpenSelector } from '../../../redux';
|
||||||
import {
|
import {
|
||||||
ChallengeFiles,
|
ChallengeFiles,
|
||||||
@ -73,7 +74,7 @@ interface EditorProps {
|
|||||||
submitChallenge: () => void;
|
submitChallenge: () => void;
|
||||||
stopResetting: () => void;
|
stopResetting: () => void;
|
||||||
tests: Test[];
|
tests: Test[];
|
||||||
theme: string;
|
theme: Themes;
|
||||||
title: string;
|
title: string;
|
||||||
updateFile: (object: {
|
updateFile: (object: {
|
||||||
fileKey: FileKey;
|
fileKey: FileKey;
|
||||||
@ -111,7 +112,7 @@ const mapStateToProps = createSelector(
|
|||||||
output: string[],
|
output: string[],
|
||||||
open,
|
open,
|
||||||
isResetting: boolean,
|
isResetting: boolean,
|
||||||
{ theme = 'default' }: { theme: string },
|
{ theme = Themes.Default }: { theme: Themes },
|
||||||
tests: [{ text: string; testString: string }]
|
tests: [{ text: string; testString: string }]
|
||||||
) => ({
|
) => ({
|
||||||
canFocus: open ? false : canFocus,
|
canFocus: open ? false : canFocus,
|
||||||
@ -994,7 +995,7 @@ const Editor = (props: EditorProps): JSX.Element => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { theme } = props;
|
const { theme } = props;
|
||||||
const editorTheme = theme === 'night' ? 'vs-dark-custom' : 'vs-custom';
|
const editorTheme = theme === Themes.Night ? 'vs-dark-custom' : 'vs-custom';
|
||||||
return (
|
return (
|
||||||
<Suspense fallback={<Loader timeout={600} />}>
|
<Suspense fallback={<Loader timeout={600} />}>
|
||||||
<span className='notranslate'>
|
<span className='notranslate'>
|
||||||
|
@ -13,8 +13,8 @@ import {
|
|||||||
take,
|
take,
|
||||||
cancel
|
cancel
|
||||||
} from 'redux-saga/effects';
|
} from 'redux-saga/effects';
|
||||||
import store from 'store';
|
|
||||||
|
|
||||||
|
import { playTone } from '../../../utils/tone';
|
||||||
import {
|
import {
|
||||||
buildChallenge,
|
buildChallenge,
|
||||||
canBuildChallenge,
|
canBuildChallenge,
|
||||||
@ -99,17 +99,10 @@ export function* executeChallengeSaga({ payload }) {
|
|||||||
yield put(updateTests(testResults));
|
yield put(updateTests(testResults));
|
||||||
|
|
||||||
const challengeComplete = testResults.every(test => test.pass && !test.err);
|
const challengeComplete = testResults.every(test => test.pass && !test.err);
|
||||||
const playSound = store.get('fcc-sound');
|
if (challengeComplete) {
|
||||||
let player;
|
playTone('tests-completed');
|
||||||
if (playSound) {
|
} else {
|
||||||
void import('tone').then(tone => {
|
playTone('tests-failed');
|
||||||
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 && payload?.showCompletionModal) {
|
if (challengeComplete && payload?.showCompletionModal) {
|
||||||
yield put(openModal('completion'));
|
yield put(openModal('completion'));
|
||||||
|
@ -4,7 +4,6 @@ import { connect } from 'react-redux';
|
|||||||
import ScrollableAnchor from 'react-scrollable-anchor';
|
import ScrollableAnchor from 'react-scrollable-anchor';
|
||||||
import { bindActionCreators, Dispatch } from 'redux';
|
import { bindActionCreators, Dispatch } from 'redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import store from 'store';
|
|
||||||
|
|
||||||
import envData from '../../../../../config/env.json';
|
import envData from '../../../../../config/env.json';
|
||||||
import { isAuditedCert } from '../../../../../utils/is-audited';
|
import { isAuditedCert } from '../../../../../utils/is-audited';
|
||||||
@ -14,6 +13,7 @@ import GreenPass from '../../../assets/icons/green-pass';
|
|||||||
import { Link } from '../../../components/helpers';
|
import { Link } from '../../../components/helpers';
|
||||||
import { completedChallengesSelector, executeGA } from '../../../redux';
|
import { completedChallengesSelector, executeGA } from '../../../redux';
|
||||||
import { ChallengeNode, CompletedChallenge } from '../../../redux/prop-types';
|
import { ChallengeNode, CompletedChallenge } from '../../../redux/prop-types';
|
||||||
|
import { playTone } from '../../../utils/tone';
|
||||||
import { makeExpandedBlockSelector, toggleBlock } from '../redux';
|
import { makeExpandedBlockSelector, toggleBlock } from '../redux';
|
||||||
import Challenges from './Challenges';
|
import Challenges from './Challenges';
|
||||||
|
|
||||||
@ -61,20 +61,7 @@ export class Block extends Component<BlockProps> {
|
|||||||
|
|
||||||
handleBlockClick(): void {
|
handleBlockClick(): void {
|
||||||
const { blockDashedName, toggleBlock, executeGA } = this.props;
|
const { blockDashedName, toggleBlock, executeGA } = this.props;
|
||||||
const playSound = store.get('fcc-sound') as boolean;
|
void playTone('block-toggle');
|
||||||
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;
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
executeGA({
|
executeGA({
|
||||||
type: 'event',
|
type: 'event',
|
||||||
data: {
|
data: {
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
SuperBlocks
|
SuperBlocks
|
||||||
} from '../../../../../config/certification-settings';
|
} from '../../../../../config/certification-settings';
|
||||||
import { createFlashMessage } from '../../../components/Flash/redux';
|
import { createFlashMessage } from '../../../components/Flash/redux';
|
||||||
|
import { FlashMessages } from '../../../components/Flash/redux/flash-messages';
|
||||||
import {
|
import {
|
||||||
userFetchStateSelector,
|
userFetchStateSelector,
|
||||||
stepsToClaimSelector,
|
stepsToClaimSelector,
|
||||||
@ -40,7 +41,7 @@ interface CertChallengeProps {
|
|||||||
|
|
||||||
const honestyInfoMessage = {
|
const honestyInfoMessage = {
|
||||||
type: 'info',
|
type: 'info',
|
||||||
message: 'flash.honest-first'
|
message: FlashMessages.HonestFirst
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state: unknown) => {
|
const mapStateToProps = (state: unknown) => {
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
import { FlashMessages } from '../components/Flash/redux/flash-messages';
|
||||||
|
|
||||||
const certificateMissingErrorMessage = {
|
const certificateMissingErrorMessage = {
|
||||||
type: 'danger',
|
type: 'danger',
|
||||||
message: 'flash.certificate-missing'
|
message: FlashMessages.CertificateMissing
|
||||||
};
|
};
|
||||||
|
|
||||||
export default certificateMissingErrorMessage;
|
export default certificateMissingErrorMessage;
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
import { FlashMessages } from '../components/Flash/redux/flash-messages';
|
||||||
|
|
||||||
const reallyWeirdErrorMessage = {
|
const reallyWeirdErrorMessage = {
|
||||||
type: 'danger',
|
type: 'danger',
|
||||||
message: 'flash.really-weird'
|
message: FlashMessages.ReallyWeird
|
||||||
};
|
};
|
||||||
|
|
||||||
export default reallyWeirdErrorMessage;
|
export default reallyWeirdErrorMessage;
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
import { FlashMessages } from '../components/Flash/redux/flash-messages';
|
||||||
|
|
||||||
const reportedErrorMessage = {
|
const reportedErrorMessage = {
|
||||||
type: 'danger',
|
type: 'danger',
|
||||||
message: 'flash.not-right'
|
message: FlashMessages.NotRight
|
||||||
};
|
};
|
||||||
|
|
||||||
export default reportedErrorMessage;
|
export default reportedErrorMessage;
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
import { FlashMessages } from '../components/Flash/redux/flash-messages';
|
||||||
|
|
||||||
const standardErrorMessage = {
|
const standardErrorMessage = {
|
||||||
type: 'danger',
|
type: 'danger',
|
||||||
message: 'flash.went-wrong'
|
message: FlashMessages.WentWrong
|
||||||
};
|
};
|
||||||
|
|
||||||
export default standardErrorMessage;
|
export default standardErrorMessage;
|
||||||
|
66
client/src/utils/tone/index.ts
Normal file
66
client/src/utils/tone/index.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user