From 16cfbd582934110a987dc1e7f635e2d4859e91db Mon Sep 17 00:00:00 2001 From: Mrugesh Mohapatra <1884376+raisedadead@users.noreply.github.com> Date: Fri, 2 Jun 2023 00:56:19 +0530 Subject: [PATCH] feat(client): add duplicate account warning (#50555) * feat(client): add duplicate account warning * feat: check completed challenge count * feat: stop redirecting /learn to /email-sign-up * test: update to account for the lack of redirects Also, in an extremely WET way, test both options. * Update client/src/pages/email-sign-up.tsx --------- Co-authored-by: Oliver Eyton-Williams --- client/i18n/locales/english/translations.json | 2 + .../Intro/components/intro-description.tsx | 6 +- client/src/components/Intro/index.tsx | 2 +- client/src/components/layouts/learn.tsx | 23 +------- .../src/components/settings/danger-zone.tsx | 2 +- .../__snapshots__/email-sign-up.test.js.snap | 31 +++++++--- client/src/pages/email-sign-up.tsx | 56 +++++++++++-------- cypress/e2e/default/user/privacy-terms.ts | 38 +++++++++++-- 8 files changed, 100 insertions(+), 60 deletions(-) diff --git a/client/i18n/locales/english/translations.json b/client/i18n/locales/english/translations.json index 0de7cc89d5f..3f3f1f35d50 100644 --- a/client/i18n/locales/english/translations.json +++ b/client/i18n/locales/english/translations.json @@ -529,6 +529,8 @@ "unsubscribed": "You have successfully been unsubscribed", "keep-coding": "Whatever you go on to, keep coding!", "email-signup": "Email Sign Up", + "brand-new-account": "Welcome to your brand new freeCodeCamp account. Let's get started.", + "duplicate-account-warning": "If you meant to sign into an existing account instead of creating this account, <0>click here to delete this account and try another email address.", "quincy": "- Quincy Larson, the teacher who founded freeCodeCamp.org", "email-blast": "By the way, each Friday I send an email with 5 links about programming and computer science. I send these to about 4 million people. Would you like me to send this to you, too?", "update-email-1": "Update your email address", diff --git a/client/src/components/Intro/components/intro-description.tsx b/client/src/components/Intro/components/intro-description.tsx index 5d30be6c6ea..06ac91f8b3b 100644 --- a/client/src/components/Intro/components/intro-description.tsx +++ b/client/src/components/Intro/components/intro-description.tsx @@ -11,7 +11,10 @@ function IntroDescription(): JSX.Element { return (
- {t('learn.read-this.heading')} + +

+ {t('learn.read-this.heading')} +

{t('learn.read-this.p1')}

{t('learn.read-this.p2')}

@@ -33,6 +36,7 @@ function IntroDescription(): JSX.Element {

{t('learn.read-this.p12')}

+ {t('misc.quincy')}
); } diff --git a/client/src/components/Intro/index.tsx b/client/src/components/Intro/index.tsx index 97ca8b48469..ccb417fcef9 100644 --- a/client/src/components/Intro/index.tsx +++ b/client/src/components/Intro/index.tsx @@ -82,7 +82,7 @@ const Intro = ({ return ( <> -

{t('learn.heading')}

+

{t('learn.heading')}

diff --git a/client/src/components/layouts/learn.tsx b/client/src/components/layouts/learn.tsx index 307fe4284ec..9c21af0b2bd 100644 --- a/client/src/components/layouts/learn.tsx +++ b/client/src/components/layouts/learn.tsx @@ -4,13 +4,8 @@ import { connect } from 'react-redux'; import { createSelector } from 'reselect'; import { Loader } from '../../components/helpers'; import { tryToShowDonationModal } from '../../redux/actions'; -import { - userFetchStateSelector, - isSignedInSelector, - userSelector -} from '../../redux/selectors'; +import { userFetchStateSelector } from '../../redux/selectors'; import DonateModal from '../Donation/donation-modal'; -import createRedirect from '../create-redirect'; import './prism.css'; import './prism-night.css'; @@ -29,12 +24,8 @@ type User = { const mapStateToProps = createSelector( userFetchStateSelector, - isSignedInSelector, - userSelector, - (fetchState: FetchState, isSignedIn, user: User) => ({ - fetchState, - isSignedIn, - user + (fetchState: FetchState) => ({ + fetchState }) ); @@ -42,8 +33,6 @@ const mapDispatchToProps = { tryToShowDonationModal }; -const RedirectEmailSignUp = createRedirect('/email-sign-up'); - type LearnLayoutProps = { isSignedIn?: boolean; fetchState: FetchState; @@ -54,9 +43,7 @@ type LearnLayoutProps = { }; function LearnLayout({ - isSignedIn, fetchState, - user, tryToShowDonationModal, children, hasEditableBoundaries @@ -79,10 +66,6 @@ function LearnLayout({ return ; } - if (isSignedIn && !user.acceptedPrivacyTerms) { - return ; - } - return ( <> diff --git a/client/src/components/settings/danger-zone.tsx b/client/src/components/settings/danger-zone.tsx index 3b5acb3333c..0bd64f60694 100644 --- a/client/src/components/settings/danger-zone.tsx +++ b/client/src/components/settings/danger-zone.tsx @@ -44,7 +44,7 @@ function DangerZone({ deleteAccount, resetProgress, t }: DangerZoneProps) { return ( - + {t('settings.danger.heading')}

{t('settings.danger.be-careful')}

diff --git a/client/src/pages/__snapshots__/email-sign-up.test.js.snap b/client/src/pages/__snapshots__/email-sign-up.test.js.snap index 10a22729799..34aad60bb0e 100644 --- a/client/src/pages/__snapshots__/email-sign-up.test.js.snap +++ b/client/src/pages/__snapshots__/email-sign-up.test.js.snap @@ -6,6 +6,7 @@ exports[` Non-Authenticated user "not accepted terms and conditio
+
@@ -16,16 +17,28 @@ exports[` Non-Authenticated user "not accepted terms and conditio className="spacer" style={ { - "padding": "15px 0", + "padding": "5px 0", } } />
- - learn.read-this.heading - +
+

+ + learn.read-this.heading + +

Non-Authenticated user "not accepted terms and conditio

learn.read-this.p12

+ + misc.quincy +

@@ -91,14 +107,11 @@ exports[` Non-Authenticated user "not accepted terms and conditio
- - misc.quincy -
@@ -109,7 +122,7 @@ exports[` Non-Authenticated user "not accepted terms and conditio className="spacer" style={ { - "padding": "15px 0", + "padding": "5px 0", } } /> diff --git a/client/src/pages/email-sign-up.tsx b/client/src/pages/email-sign-up.tsx index bd62bfe9948..0425eef67ea 100644 --- a/client/src/pages/email-sign-up.tsx +++ b/client/src/pages/email-sign-up.tsx @@ -2,14 +2,14 @@ import { Row, Col, Button, Grid } from '@freecodecamp/react-bootstrap'; import React, { useEffect, useRef } from 'react'; import Helmet from 'react-helmet'; import type { TFunction } from 'i18next'; -import { withTranslation } from 'react-i18next'; +import { withTranslation, Trans } from 'react-i18next'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import type { Dispatch } from 'redux'; import { createSelector } from 'reselect'; import IntroDescription from '../components/Intro/components/intro-description'; import createRedirect from '../components/create-redirect'; -import { Spacer, Loader } from '../components/helpers'; +import { Spacer, Loader, Link } from '../components/helpers'; import { apiLocation } from '../../../config/env.json'; import { acceptTerms } from '../redux/actions'; @@ -26,6 +26,7 @@ interface AcceptPrivacyTermsProps { isSignedIn: boolean; t: TFunction; showLoading: boolean; + completedChallengeCount?: number; } const mapStateToProps = createSelector( @@ -33,13 +34,17 @@ const mapStateToProps = createSelector( isSignedInSelector, signInLoadingSelector, ( - { acceptedPrivacyTerms }: { acceptedPrivacyTerms: boolean }, + { + acceptedPrivacyTerms, + completedChallengeCount + }: { acceptedPrivacyTerms: boolean; completedChallengeCount: number }, isSignedIn: boolean, showLoading: boolean ) => ({ acceptedPrivacyTerms, isSignedIn, - showLoading + showLoading, + completedChallengeCount }) ); const mapDispatchToProps = (dispatch: Dispatch) => @@ -51,7 +56,8 @@ function AcceptPrivacyTerms({ acceptedPrivacyTerms, isSignedIn, t, - showLoading + showLoading, + completedChallengeCount = 0 }: AcceptPrivacyTermsProps) { const acceptedPrivacyRef = useRef(acceptedPrivacyTerms); const acceptTermsRef = useRef(acceptTerms); @@ -61,24 +67,11 @@ function AcceptPrivacyTerms({ acceptTermsRef.current = acceptTerms; }); - useEffect(() => { - return () => { - // if a user navigates away from here we should set acceptedPrivacyTerms - // to true (so they do not get pulled back) without changing their email - // preferences (hence the null payload) - // This makes sure that the user has to opt in to Quincy's emails and that - // they are only asked twice - if (!acceptedPrivacyRef.current) { - acceptTermsRef.current(null); - } - }; - }, []); - function onClick(isWeeklyEmailAccepted: boolean) { acceptTerms(isWeeklyEmailAccepted); } - function renderEmailListOptin(isSignedIn: boolean, showLoading: boolean) { + function renderEmailListOptIn(isSignedIn: boolean, showLoading: boolean) { if (showLoading) { return ; } @@ -138,21 +131,36 @@ function AcceptPrivacyTerms({ {t('misc.email-signup')} | freeCodeCamp.org + {isSignedIn && completedChallengeCount < 1 ? ( + + + +

{t('misc.brand-new-account')}

+ +

+ + + +

+ +
+ ) : ( + '' + )} - +
- {t('misc.quincy')} - +

{t('misc.email-blast')}

- + - {renderEmailListOptin(isSignedIn, showLoading)} + {renderEmailListOptIn(isSignedIn, showLoading)} diff --git a/cypress/e2e/default/user/privacy-terms.ts b/cypress/e2e/default/user/privacy-terms.ts index 0fec3116ba9..07a553c4976 100644 --- a/cypress/e2e/default/user/privacy-terms.ts +++ b/cypress/e2e/default/user/privacy-terms.ts @@ -1,5 +1,6 @@ +// TODO: DRY out the parts before clicking "Yes please" and "No thanks" describe('Privacy terms', () => { - it('should not redirect away from email sign up page on login', () => { + it('should accept update privacy terms if requests emails from Quincy', () => { // Flag used to identify if the `/update-privacy-terms` have been called let privacyTermsUpdated = false; cy.intercept('PUT', '/update-privacy-terms', () => { @@ -13,13 +14,13 @@ describe('Privacy terms', () => { // 2. The /update-privacy-terms has not been requested cy.visit('/'); cy.get('[data-test-label="landing-small-cta"]').click(); - cy.location('pathname').should('contain', '/email-sign-up'); + // Since we're using the dev login, we do cy.wrap(privacyTermsUpdated).should('eq', false); + cy.visit('/email-sign-up'); // Assert email sign up elements and make sure we don't get redirected somewhere else cy.title().should('contain', 'Email Sign Up'); cy.get('[data-cy="email-sign-up"]').should('exist'); - // Navigate away from this page via quincy emails which should unmount the component - // and request /update-privacy-terms + // Accept cy.get('button:contains("Yes please")').click(); cy.wait('@updatePrivacyTerms').then(() => { expect(privacyTermsUpdated).to.eq(true); @@ -27,4 +28,33 @@ describe('Privacy terms', () => { cy.location('pathname').should('contain', '/learn'); }); }); + + it('should accept update privacy terms if the user rejects emails from Quincy', () => { + // Flag used to identify if the `/update-privacy-terms` have been called + let privacyTermsUpdated = false; + cy.intercept('PUT', '/update-privacy-terms', () => { + privacyTermsUpdated = true; + }).as('updatePrivacyTerms'); + + // Seed dev user with `acceptedPrivacyTerms` unset + cy.exec('pnpm run seed -- --unset-privacy-terms'); + // Go to the homepage and log in manually so we can assert the following: + // 1. Redirection to /email-sign-up works properly + // 2. The /update-privacy-terms has not been requested + cy.visit('/'); + cy.get('[data-test-label="landing-small-cta"]').click(); + // Since we're using the dev login, we do + cy.wrap(privacyTermsUpdated).should('eq', false); + cy.visit('/email-sign-up'); + // Assert email sign up elements and make sure we don't get redirected somewhere else + cy.title().should('contain', 'Email Sign Up'); + cy.get('[data-cy="email-sign-up"]').should('exist'); + // Accept + cy.get('button:contains("No thanks")').click(); + cy.wait('@updatePrivacyTerms').then(() => { + expect(privacyTermsUpdated).to.eq(true); + cy.contains('Welcome back'); + cy.location('pathname').should('contain', '/learn'); + }); + }); });