mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 18:26:54 +00:00
feat: add email sign up alert (#61218)
Co-authored-by: Niraj Nandish <nirajnandish@icloud.com> Co-authored-by: Mrugesh Mohapatra <1884376+raisedadead@users.noreply.github.com> Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com> Co-authored-by: Shaun Hamilton <shauhami020@gmail.com>
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Container } from '@freecodecamp/ui';
|
||||
import EmailOptions from '../email-options';
|
||||
import { updateMyQuincyEmail } from '../../redux/settings/actions';
|
||||
import { userSelector, isSignedInSelector } from '../../redux/selectors';
|
||||
import { CompletedChallenge } from '../../redux/prop-types';
|
||||
|
||||
interface EmailSignUpAlertProps {
|
||||
updateQuincyEmail: (isSendQuincyEmail: boolean) => void;
|
||||
sendQuincyEmail: boolean | null;
|
||||
isSignedIn: boolean;
|
||||
completedChallengesCount: number;
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: unknown) => {
|
||||
const user = userSelector(state) as {
|
||||
sendQuincyEmail: boolean | null;
|
||||
completedChallenges: CompletedChallenge[];
|
||||
};
|
||||
return {
|
||||
sendQuincyEmail: user.sendQuincyEmail,
|
||||
isSignedIn: isSignedInSelector(state),
|
||||
completedChallengesCount: user.completedChallenges.length
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = {
|
||||
updateQuincyEmail: (sendQuincyEmail: boolean) =>
|
||||
updateMyQuincyEmail({ sendQuincyEmail })
|
||||
};
|
||||
|
||||
function EmailSignUpAlert({
|
||||
updateQuincyEmail,
|
||||
sendQuincyEmail,
|
||||
isSignedIn,
|
||||
completedChallengesCount = 0
|
||||
}: EmailSignUpAlertProps) {
|
||||
const newAccount = isSignedIn && completedChallengesCount < 1;
|
||||
const userHasMadeEmailSelection = sendQuincyEmail !== null;
|
||||
|
||||
if (userHasMadeEmailSelection || newAccount) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Container fluid={true} className='email-sign-up-alert'>
|
||||
<EmailOptions
|
||||
isSignedIn={isSignedIn}
|
||||
updateQuincyEmail={updateQuincyEmail}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(EmailSignUpAlert);
|
||||
@@ -6,6 +6,7 @@ import Login from '../Header/components/login';
|
||||
import { Link, Loader } from '../helpers';
|
||||
|
||||
import './intro.css';
|
||||
import EmailSignUpAlert from './email-sign-up-alert';
|
||||
import LearnAlert from './learn-alert';
|
||||
|
||||
interface IntroProps {
|
||||
@@ -64,6 +65,7 @@ const Intro = ({
|
||||
onLearnDonationAlertClick={onLearnDonationAlertClick}
|
||||
isDonating={isDonating}
|
||||
/>
|
||||
<EmailSignUpAlert />
|
||||
{completedChallengeCount && slug && completedChallengeCount < 15 ? (
|
||||
<div className='intro-description'>
|
||||
<Spacer size='m' />
|
||||
|
||||
@@ -42,3 +42,20 @@
|
||||
font-style: normal;
|
||||
color: var(--secondary-color);
|
||||
}
|
||||
|
||||
.email-sign-up-alert {
|
||||
padding: 20px;
|
||||
border: 1px solid var(--quaternary-color);
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.email-list-opt {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.message-author {
|
||||
display: block;
|
||||
text-align: center;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@@ -7,8 +7,11 @@ import Intro from '.';
|
||||
|
||||
jest.mock('../../analytics');
|
||||
|
||||
function renderWithRedux(ui: JSX.Element) {
|
||||
return render(<Provider store={createStore()}>{ui}</Provider>);
|
||||
function renderWithRedux(
|
||||
ui: JSX.Element,
|
||||
preloadedState: Record<string, unknown> = {}
|
||||
) {
|
||||
return render(<Provider store={createStore(preloadedState)}>{ui}</Provider>);
|
||||
}
|
||||
|
||||
describe('<Intro />', () => {
|
||||
@@ -19,7 +22,19 @@ describe('<Intro />', () => {
|
||||
});
|
||||
|
||||
it('has a blockquote when loggedIn', () => {
|
||||
renderWithRedux(<Intro {...loggedInProps} />);
|
||||
// Provide a minimal preloaded state so connected components expecting a
|
||||
// sessionUser (e.g. EmailSignUpAlert) do not receive null.
|
||||
const preloadedState = {
|
||||
app: {
|
||||
user: {
|
||||
sessionUser: {
|
||||
completedChallenges: [{}],
|
||||
sendQuincyEmail: null
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
renderWithRedux(<Intro {...loggedInProps} />, preloadedState);
|
||||
expect(screen.getByTestId('quote-block')).toBeInTheDocument();
|
||||
expect(screen.getByRole('heading', { level: 1 })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Col, Row, Button, Spacer } from '@freecodecamp/ui';
|
||||
import { apiLocation } from '../../config/env.json';
|
||||
|
||||
interface EmailListOptInProps {
|
||||
isSignedIn: boolean;
|
||||
updateQuincyEmail: (isSendQuincyEmail: boolean) => void;
|
||||
}
|
||||
|
||||
export function EmailListOptIn({
|
||||
isSignedIn,
|
||||
updateQuincyEmail
|
||||
}: EmailListOptInProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (isSignedIn) {
|
||||
return (
|
||||
<Row className='email-list-opt'>
|
||||
<Col md={4} mdOffset={2} sm={5} smOffset={1} xs={12}>
|
||||
<Button
|
||||
block={true}
|
||||
variant='primary'
|
||||
onClick={() => updateQuincyEmail(true)}
|
||||
>
|
||||
{t('buttons.yes-please')}
|
||||
</Button>
|
||||
<Spacer size='xs' />
|
||||
</Col>
|
||||
<Col md={4} sm={5} xs={12}>
|
||||
<Button
|
||||
block={true}
|
||||
variant='primary'
|
||||
onClick={() => updateQuincyEmail(false)}
|
||||
>
|
||||
{t('buttons.no-thanks')}
|
||||
</Button>
|
||||
<Spacer size='xs' />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Row>
|
||||
<Col md={8} mdOffset={2} sm={10} smOffset={1} xs={12}>
|
||||
<Spacer size='xs' />
|
||||
<Button block={true} variant='primary' href={`${apiLocation}/signin`}>
|
||||
{t('buttons.sign-up-email-list')}
|
||||
</Button>
|
||||
<Spacer size='xs' />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
interface EmailOptionsProps {
|
||||
isSignedIn: boolean;
|
||||
updateQuincyEmail: (isSendQuincyEmail: boolean) => void;
|
||||
isPage?: boolean;
|
||||
}
|
||||
|
||||
function EmailOptions({
|
||||
isSignedIn,
|
||||
updateQuincyEmail,
|
||||
isPage
|
||||
}: EmailOptionsProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row>
|
||||
<Col xs={12}>
|
||||
{isPage ? (
|
||||
<h1 className='text-center'>{t('misc.email-signup')}</h1>
|
||||
) : (
|
||||
<h2 className='text-center'>{t('misc.email-signup')}</h2>
|
||||
)}
|
||||
<Spacer size='xs' />
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row>
|
||||
<Col
|
||||
{...(isPage ? { md: 8, mdOffset: 2, sm: 10, smOffset: 1 } : {})}
|
||||
xs={12}
|
||||
>
|
||||
<p>{t('misc.email-blast')}</p>
|
||||
<span className='message-author'>{t('misc.quincy')}</span>
|
||||
<Spacer size='m' />
|
||||
</Col>
|
||||
</Row>
|
||||
<EmailListOptIn
|
||||
isSignedIn={isSignedIn}
|
||||
updateQuincyEmail={updateQuincyEmail}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default EmailOptions;
|
||||
@@ -11,7 +11,6 @@ window.___loader = { enqueue: () => {}, hovering: () => {} };
|
||||
|
||||
const userProps = {
|
||||
user: {
|
||||
acceptedPrivacyTerms: true,
|
||||
currentChallengeId: 'string',
|
||||
email: 'string',
|
||||
emailVerified: true,
|
||||
|
||||
@@ -32,7 +32,7 @@ const mapDispatchToProps = (dispatch: Dispatch) =>
|
||||
type EmailProps = {
|
||||
email: string;
|
||||
isEmailVerified: boolean;
|
||||
sendQuincyEmail: boolean;
|
||||
sendQuincyEmail: boolean | null;
|
||||
t: TFunction;
|
||||
updateMyEmail: (email: string) => void;
|
||||
updateQuincyEmail: (sendQuincyEmail: boolean) => void;
|
||||
@@ -250,7 +250,7 @@ function EmailSettings({
|
||||
<FullWidthRow>
|
||||
<ToggleButtonSetting
|
||||
action={t('settings.email.weekly')}
|
||||
flag={sendQuincyEmail}
|
||||
flag={!!sendQuincyEmail}
|
||||
flagName='sendQuincyEmail'
|
||||
offLabel={t('buttons.no-thanks')}
|
||||
onLabel={t('buttons.yes-please')}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
.email-sign-up strong,
|
||||
.email-sign-up p {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/*
|
||||
This is a temporary fix until the component library is revisited.
|
||||
See https://github.com/freeCodeCamp/freeCodeCamp/issues/52131#issuecomment-1788840851.
|
||||
*/
|
||||
.email-list-opt {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
@media (min-width: 500px) {
|
||||
.email-sign-up strong,
|
||||
.email-sign-up p {
|
||||
font-size: 1.17rem;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,14 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import React from 'react';
|
||||
import Helmet from 'react-helmet';
|
||||
import { useTranslation, Trans } from 'react-i18next';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import type { Dispatch } from 'redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { Container, Col, Row, Button, Spacer } from '@freecodecamp/ui';
|
||||
import { Container, Spacer } from '@freecodecamp/ui';
|
||||
|
||||
import createRedirect from '../components/create-redirect';
|
||||
import { Loader, Link } from '../components/helpers';
|
||||
import { apiLocation } from '../../config/env.json';
|
||||
|
||||
import { acceptTerms } from '../redux/actions';
|
||||
import { Loader } from '../components/helpers';
|
||||
import EmailOptions from '../components/email-options';
|
||||
import { updateMyQuincyEmail } from '../redux/settings/actions';
|
||||
import {
|
||||
signInLoadingSelector,
|
||||
userSelector,
|
||||
@@ -19,13 +16,11 @@ import {
|
||||
} from '../redux/selectors';
|
||||
import type { User } from '../redux/prop-types';
|
||||
|
||||
import './email-sign-up.css';
|
||||
interface AcceptPrivacyTermsProps {
|
||||
acceptTerms: (accept: boolean | null) => void;
|
||||
acceptedPrivacyTerms: boolean;
|
||||
interface EmailSignUpProps {
|
||||
updateQuincyEmail: (isSendQuincyEmail: boolean) => void;
|
||||
sendQuincyEmail: boolean | null | undefined;
|
||||
isSignedIn: boolean;
|
||||
showLoading: boolean;
|
||||
completedChallengeCount: number;
|
||||
}
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
@@ -33,90 +28,28 @@ const mapStateToProps = createSelector(
|
||||
isSignedInSelector,
|
||||
signInLoadingSelector,
|
||||
(user: User | null, isSignedIn: boolean, showLoading: boolean) => ({
|
||||
acceptedPrivacyTerms: !!user?.acceptedPrivacyTerms,
|
||||
sendQuincyEmail: user?.sendQuincyEmail,
|
||||
isSignedIn,
|
||||
showLoading,
|
||||
completedChallengeCount: user?.completedChallengeCount ?? 0
|
||||
showLoading
|
||||
})
|
||||
);
|
||||
const mapDispatchToProps = (dispatch: Dispatch) =>
|
||||
bindActionCreators({ acceptTerms }, dispatch);
|
||||
const mapDispatchToProps = {
|
||||
updateQuincyEmail: (sendQuincyEmail: boolean) =>
|
||||
updateMyQuincyEmail({ sendQuincyEmail })
|
||||
};
|
||||
const RedirectToLearn = createRedirect('/learn');
|
||||
|
||||
function EmailListOptIn({
|
||||
function EmailSignUp({
|
||||
updateQuincyEmail,
|
||||
sendQuincyEmail,
|
||||
isSignedIn,
|
||||
acceptTerms
|
||||
}: {
|
||||
isSignedIn: boolean;
|
||||
acceptTerms: (accepted: boolean) => void;
|
||||
}) {
|
||||
showLoading
|
||||
}: EmailSignUpProps) {
|
||||
const { t } = useTranslation();
|
||||
if (isSignedIn) {
|
||||
return (
|
||||
<Container>
|
||||
<Row className='email-list-opt'>
|
||||
<Col md={4} mdOffset={2} sm={5} smOffset={1} xs={12}>
|
||||
<Button
|
||||
block={true}
|
||||
size='large'
|
||||
variant='primary'
|
||||
onClick={() => acceptTerms(true)}
|
||||
>
|
||||
{t('buttons.yes-please')}
|
||||
</Button>
|
||||
<Spacer size='xs' />
|
||||
</Col>
|
||||
<Col md={4} sm={5} xs={12}>
|
||||
<Button
|
||||
block={true}
|
||||
size='large'
|
||||
variant='primary'
|
||||
onClick={() => acceptTerms(false)}
|
||||
>
|
||||
{t('buttons.no-thanks')}
|
||||
</Button>
|
||||
<Spacer size='xs' />
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Col md={8} mdOffset={2} sm={10} smOffset={1} xs={12}>
|
||||
<Spacer size='xs' />
|
||||
<Button
|
||||
block={true}
|
||||
size='large'
|
||||
variant='primary'
|
||||
href={`${apiLocation}/signin`}
|
||||
>
|
||||
{t('buttons.sign-up-email-list')}
|
||||
</Button>
|
||||
<Spacer size='xs' />
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function AcceptPrivacyTerms({
|
||||
acceptTerms,
|
||||
acceptedPrivacyTerms,
|
||||
isSignedIn,
|
||||
showLoading,
|
||||
completedChallengeCount
|
||||
}: AcceptPrivacyTermsProps) {
|
||||
const { t } = useTranslation();
|
||||
const acceptedPrivacyRef = useRef(acceptedPrivacyTerms);
|
||||
const acceptTermsRef = useRef(acceptTerms);
|
||||
const newAccount = isSignedIn && completedChallengeCount < 1;
|
||||
const userHasMadeSelection = isSignedIn && sendQuincyEmail !== null;
|
||||
|
||||
useEffect(() => {
|
||||
acceptedPrivacyRef.current = acceptedPrivacyTerms;
|
||||
acceptTermsRef.current = acceptTerms;
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return acceptedPrivacyTerms ? (
|
||||
return userHasMadeSelection ? (
|
||||
<RedirectToLearn />
|
||||
) : (
|
||||
<>
|
||||
@@ -124,47 +57,20 @@ function AcceptPrivacyTerms({
|
||||
<title>{t('misc.email-signup')} | freeCodeCamp.org</title>
|
||||
</Helmet>
|
||||
<Container>
|
||||
<Row>
|
||||
<Col md={8} mdOffset={2} sm={10} smOffset={1} xs={12}>
|
||||
<Spacer size='l' />
|
||||
<h1 className='text-center'>
|
||||
{newAccount
|
||||
? t('misc.brand-new-account')
|
||||
: t('misc.email-signup')}
|
||||
</h1>
|
||||
<Spacer size='xs' />
|
||||
</Col>
|
||||
</Row>
|
||||
{newAccount && (
|
||||
<Row>
|
||||
<Col md={8} mdOffset={2} sm={10} smOffset={1} xs={12}>
|
||||
<p>
|
||||
<Trans i18nKey='misc.duplicate-account-warning'>
|
||||
<Link className='inline' to='/settings#danger-zone' />
|
||||
</Trans>
|
||||
</p>
|
||||
<hr />
|
||||
</Col>
|
||||
</Row>
|
||||
<Spacer size='l' />
|
||||
{showLoading ? (
|
||||
<Loader fullScreen={true} />
|
||||
) : (
|
||||
<EmailOptions
|
||||
isSignedIn={isSignedIn}
|
||||
updateQuincyEmail={updateQuincyEmail}
|
||||
isPage={true}
|
||||
/>
|
||||
)}
|
||||
<Row className='email-sign-up'>
|
||||
<Col md={8} mdOffset={2} sm={10} smOffset={1} xs={12}>
|
||||
<Spacer size='xs' />
|
||||
<p>{t('misc.email-blast')}</p>
|
||||
<Spacer size='xs' />
|
||||
</Col>
|
||||
{showLoading ? (
|
||||
<Loader fullScreen={true} />
|
||||
) : (
|
||||
<EmailListOptIn isSignedIn={isSignedIn} acceptTerms={acceptTerms} />
|
||||
)}
|
||||
<Col xs={12}>
|
||||
<Spacer size='m' />
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
<Spacer size='l' />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(AcceptPrivacyTerms);
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(EmailSignUp);
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
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 './actions';
|
||||
|
||||
function* acceptTermsSaga({ payload: quincyEmails }) {
|
||||
try {
|
||||
const { data } = yield call(putUserAcceptsTerms, quincyEmails);
|
||||
|
||||
yield put(acceptTermsComplete(quincyEmails));
|
||||
yield put(createFlashMessage(data));
|
||||
} catch (e) {
|
||||
yield put(acceptTermsError(e));
|
||||
}
|
||||
}
|
||||
|
||||
function* acceptCompleteSaga() {
|
||||
yield call(navigate, '/learn');
|
||||
}
|
||||
|
||||
export function createAcceptTermsSaga(types) {
|
||||
return [
|
||||
takeEvery(types.acceptTerms, acceptTermsSaga),
|
||||
takeEvery(types.acceptTermsComplete, acceptCompleteSaga)
|
||||
];
|
||||
}
|
||||
@@ -42,7 +42,6 @@ export const actionTypes = createTypes(
|
||||
...createAsyncTypes('fetchUser'),
|
||||
...createAsyncTypes('postCharge'),
|
||||
...createAsyncTypes('fetchProfileForUser'),
|
||||
...createAsyncTypes('acceptTerms'),
|
||||
...createAsyncTypes('showCert'),
|
||||
...createAsyncTypes('reportUser'),
|
||||
...createAsyncTypes('deleteUserToken'),
|
||||
|
||||
@@ -42,12 +42,6 @@ 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 fetchUserTimeout = createAction(actionTypes.fetchUserTimeout);
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
CURRENT_CHALLENGE_KEY
|
||||
} from '../templates/Challenges/redux/action-types';
|
||||
import { getIsDailyCodingChallenge } from '../../../shared/config/challenge-types';
|
||||
import { createAcceptTermsSaga } from './accept-terms-saga';
|
||||
import { actionTypes, ns as MainApp } from './action-types';
|
||||
import { createAppMountSaga } from './app-mount-saga';
|
||||
import { createDonationSaga } from './donation-saga';
|
||||
@@ -88,7 +87,6 @@ const initialState = {
|
||||
export const epics = [hardGoToEpic, failedUpdatesEpic, updateCompleteEpic];
|
||||
|
||||
export const sagas = [
|
||||
...createAcceptTermsSaga(actionTypes),
|
||||
...createThemeSaga(actionTypes),
|
||||
...createAppMountSaga(actionTypes),
|
||||
...createDonationSaga(actionTypes),
|
||||
@@ -117,26 +115,6 @@ function spreadThePayloadOnUser(state, payload) {
|
||||
|
||||
export const reducer = handleActions(
|
||||
{
|
||||
[actionTypes.acceptTermsComplete]: (state, { payload }) => {
|
||||
return {
|
||||
...state,
|
||||
user: {
|
||||
...state.user,
|
||||
sessionUser: {
|
||||
...state.user.sessionUser,
|
||||
// TODO: the user accepts the privacy terms in practice during auth
|
||||
// however, it's currently being used to track if they've accepted
|
||||
// or rejected the newsletter. Ideally this should be migrated,
|
||||
// since they can't sign up without accepting the terms.
|
||||
acceptedPrivacyTerms: true,
|
||||
sendQuincyEmail:
|
||||
payload === null
|
||||
? state.user.sessionUser.sendQuincyEmail
|
||||
: payload
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
[actionTypes.allowSectionDonationRequests]: (state, { payload }) => {
|
||||
return {
|
||||
...state,
|
||||
|
||||
@@ -393,7 +393,7 @@ export type User = {
|
||||
profileUI: ProfileUI;
|
||||
progressTimestamps: Array<unknown>;
|
||||
savedChallenges: SavedChallenges;
|
||||
sendQuincyEmail: boolean;
|
||||
sendQuincyEmail: boolean | null;
|
||||
sound: boolean;
|
||||
theme: UserThemes;
|
||||
keyboardShortcuts: boolean;
|
||||
|
||||
@@ -361,9 +361,9 @@ export function putUpdateMyHonesty(
|
||||
return put('/update-my-honesty', update);
|
||||
}
|
||||
|
||||
export function putUpdateMyQuincyEmail(
|
||||
update: Record<string, string>
|
||||
): Promise<ResponseWithData<void>> {
|
||||
export function putUpdateMyQuincyEmail(update: {
|
||||
sendQuincyEmail: boolean;
|
||||
}): Promise<ResponseWithData<void>> {
|
||||
return put('/update-my-quincy-email', update);
|
||||
}
|
||||
|
||||
@@ -373,12 +373,6 @@ export function putUpdateMyPortfolio(
|
||||
return put('/update-my-portfolio', update);
|
||||
}
|
||||
|
||||
export function putUserAcceptsTerms(
|
||||
quincyEmails: boolean
|
||||
): Promise<ResponseWithData<void>> {
|
||||
return put('/update-privacy-terms', { quincyEmails });
|
||||
}
|
||||
|
||||
export function putUserUpdateEmail(
|
||||
email: string
|
||||
): Promise<ResponseWithData<void>> {
|
||||
|
||||
Reference in New Issue
Block a user