From 699de05989742eaaf4914e69a999622a306e2df5 Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Fri, 29 May 2020 12:20:59 +0300 Subject: [PATCH 1/9] Implement #4374 --- .../__snapshots__/TopcoderFooter.jsx.snap | 21 +++++++++++++------ .../components/TopcoderFooter/index.jsx | 7 ++++--- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/__tests__/shared/components/__snapshots__/TopcoderFooter.jsx.snap b/__tests__/shared/components/__snapshots__/TopcoderFooter.jsx.snap index 9fdfd6c76a..f95e017b49 100644 --- a/__tests__/shared/components/__snapshots__/TopcoderFooter.jsx.snap +++ b/__tests__/shared/components/__snapshots__/TopcoderFooter.jsx.snap @@ -149,18 +149,18 @@ exports[`Matches shallow shapshot 1`] = ` className="src-shared-components-TopcoderFooter-___style__link___3-nzm" > - TCO + Blog
  • - Programs + Events Calendar
  • +
  • + + Programs + +
  • @@ -185,9 +194,9 @@ exports[`Matches shallow shapshot 1`] = ` className="src-shared-components-TopcoderFooter-___style__link___3-nzm" > - Blog + TCO
  • COMMUNITY
      - TCO - Programs + Blog + Events Calendar Forums + Programs Statistics - Blog + TCO Thrive
    From 5c3b4683cf2f6b1184dd55647f96c3148a5b4999 Mon Sep 17 00:00:00 2001 From: Sushil Shinde Date: Fri, 29 May 2020 20:58:37 +0530 Subject: [PATCH 2/9] ci: removed feature-contenful from deployment config --- .circleci/config.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2540011007..10da5c46c7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -238,7 +238,6 @@ workflows: branches: only: - develop - - feature-contentful # This is alternate dev env for parallel testing - "build-qa": context : org-global From 0055da438ca08c3a751bbc27fb60b558fb6d21f3 Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Mon, 1 Jun 2020 10:15:06 +0300 Subject: [PATCH 3/9] Updates for #4439 --- ...om-inline-components-in-markdown-fields.md | 6 +- .../ConfirmModal/index.jsx | 12 ++-- .../NewsletterSignupForMembers/index.jsx | 68 +++++++++++-------- .../containers/NewsletterSignupForMembers.jsx | 6 ++ 4 files changed, 58 insertions(+), 34 deletions(-) diff --git a/docs/contentful/custom-inline-components-in-markdown-fields.md b/docs/contentful/custom-inline-components-in-markdown-fields.md index 9b6b861a34..da252c165b 100644 --- a/docs/contentful/custom-inline-components-in-markdown-fields.md +++ b/docs/contentful/custom-inline-components-in-markdown-fields.md @@ -80,8 +80,10 @@ other types too. | --- | --- | --- | | label | Subscribe for Newsletter | Optional. Custom label to show on the button. | | listId | | ID of MailChimp list to subscribe. | - | interests | empty string | Optional. commas separated string of group ids to which user should be subscribed | - + | tags | | ID of MailChimp tags to subscribe. | + | buttonTheme | primary-green-md | Theme key(`tc-` is omitted) for the button. See https://community-app.topcoder.com/examples/contentful/contentblock/3k7k1JpnSvIRrJYWs4izYi | + | title | Sign up for the Topcoder Newsletter | Modal title | + | desc | Do you want to subscribe to this newsletter? | Modal description | - #### VideoModalButton *Example:* `` diff --git a/src/shared/components/NewsletterSignupForMembers/ConfirmModal/index.jsx b/src/shared/components/NewsletterSignupForMembers/ConfirmModal/index.jsx index 1329dfcb53..68c49a979e 100644 --- a/src/shared/components/NewsletterSignupForMembers/ConfirmModal/index.jsx +++ b/src/shared/components/NewsletterSignupForMembers/ConfirmModal/index.jsx @@ -34,13 +34,15 @@ function ConfirmModal({ skipConfirmSignup, token, theme, + title, + desc, }) { let text; if (token) { text = (
    -

    Sign up for the Topcoder Newsletter

    -

    Do you want to subscribe to this newsletter?

    +

    {title}

    +

    {desc}

    ); if (skipConfirmSignup) { @@ -49,7 +51,7 @@ function ConfirmModal({ } else { text = customTcAuthModalText || (
    -

    Sign up for the Topcoder Newsletter

    +

    {title}

    You must be a Topcoder member before you can signup for Newsletter. To signup, login if you are already a member. If not, register first. @@ -84,7 +86,7 @@ function ConfirmModal({ button: buttonThemes.tc['primary-green-md'], }} > - SIGNUP + Ok

    ) : ( @@ -130,6 +132,8 @@ ConfirmModal.propTypes = { skipConfirmSignup: PT.bool.isRequired, theme: PT.shape().isRequired, token: PT.string, + title: PT.string.isRequired, + desc: PT.string.isRequired, }; export default themr('NewsletterSignupForMembers-Modal', defaultStyle)(ConfirmModal); diff --git a/src/shared/components/NewsletterSignupForMembers/index.jsx b/src/shared/components/NewsletterSignupForMembers/index.jsx index 8ea8712c0b..085f45d634 100644 --- a/src/shared/components/NewsletterSignupForMembers/index.jsx +++ b/src/shared/components/NewsletterSignupForMembers/index.jsx @@ -46,37 +46,44 @@ export default function NewsletterSignupForMembers({ state, theme, token, + buttonTheme, + title, + desc, }) { return (
    - { - switch (state) { - case STATE.SIGNEDUP: - case STATE.SIGNING: - return; - default: - } - showSignupConfirmModal(); - }} - className={state === STATE.SIGNING ? style.signing : ''} - theme={{ - button: buttonThemes.tc['primary-green-md'], - disabled: buttonThemes.tc.themedButtonDisabled, - }} - > - {state === STATE.SIGNING ? ( -
    - - Signing... - - -
    - ) : label} -
    + { + state !== STATE.HIDDEN ? ( + { + switch (state) { + case STATE.SIGNEDUP: + case STATE.SIGNING: + return; + default: + } + showSignupConfirmModal(); + }} + className={state === STATE.SIGNING ? style.signing : ''} + theme={{ + button: buttonThemes.tc[buttonTheme], + disabled: buttonThemes.tc.themedButtonDisabled, + }} + > + {state === STATE.SIGNING ? ( +
    + + Signing... + + +
    + ) : label} +
    + ) : null + } {state === STATE.SIGNEDUP ? (

    Congratulations!

    -

    +

    { customSignupConfirmationText - || 'You are subscribed to the newsletter.' + || 'You are now subscribed.' }

    @@ -110,6 +117,8 @@ export default function NewsletterSignupForMembers({ resetSignupButton={resetSignupButton} skipConfirmSignup={skipConfirmSignup} token={token} + title={title} + desc={desc} /> ) : null} {state === STATE.ERROR ? ( @@ -164,4 +173,7 @@ NewsletterSignupForMembers.propTypes = { state: PT.oneOf(_.values(STATE)).isRequired, theme: PT.shape(), token: PT.string, + buttonTheme: PT.string.isRequired, + title: PT.string.isRequired, + desc: PT.string.isRequired, }; diff --git a/src/shared/containers/NewsletterSignupForMembers.jsx b/src/shared/containers/NewsletterSignupForMembers.jsx index f656caa187..922eca698d 100644 --- a/src/shared/containers/NewsletterSignupForMembers.jsx +++ b/src/shared/containers/NewsletterSignupForMembers.jsx @@ -185,6 +185,9 @@ NewsletterSignupForMembersContainer.defaultProps = { label: 'Subscribe for Newsletter', tags: '', user: null, + buttonTheme: 'primary-green-md', + title: 'Sign up for the Topcoder Newsletter', + desc: 'Do you want to subscribe to this newsletter?', }; NewsletterSignupForMembersContainer.propTypes = { @@ -194,6 +197,9 @@ NewsletterSignupForMembersContainer.propTypes = { tags: PT.string, listId: PT.string.isRequired, user: PT.shape(), + buttonTheme: PT.string, + title: PT.string, + desc: PT.string, }; function mapStateToProps(state, ownProps) { From f6929947d9307d129dade8a5b5348774fbaf8dc2 Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Thu, 4 Jun 2020 14:25:58 +0300 Subject: [PATCH 4/9] Implements #4165 --- .../Email/__snapshots__/index.jsx.snap | 70 ++++++++ .../Settings/Preferences/Email/index.jsx | 11 ++ .../Preferences/__snapshots__/index.jsx.snap | 50 ------ .../components/Settings/Preferences/index.jsx | 11 -- config/default.js | 1 + src/shared/actions/newsletterPreferences.js | 104 ++++++++++++ .../Settings/Preferences/Email/index.jsx | 150 ++++++++++++------ .../components/Settings/Preferences/index.jsx | 6 +- .../Settings/ToggleableItem/index.jsx | 3 +- .../containers/NewsletterPreferences.jsx | 88 ++++++++++ src/shared/containers/Settings.jsx | 7 +- src/shared/reducers/index.js | 2 + src/shared/reducers/newsletterPreferences.js | 74 +++++++++ 13 files changed, 456 insertions(+), 121 deletions(-) create mode 100644 __tests__/shared/components/Settings/Preferences/Email/__snapshots__/index.jsx.snap create mode 100644 __tests__/shared/components/Settings/Preferences/Email/index.jsx delete mode 100644 __tests__/shared/components/Settings/Preferences/__snapshots__/index.jsx.snap delete mode 100644 __tests__/shared/components/Settings/Preferences/index.jsx create mode 100644 src/shared/actions/newsletterPreferences.js create mode 100644 src/shared/containers/NewsletterPreferences.jsx create mode 100644 src/shared/reducers/newsletterPreferences.js diff --git a/__tests__/shared/components/Settings/Preferences/Email/__snapshots__/index.jsx.snap b/__tests__/shared/components/Settings/Preferences/Email/__snapshots__/index.jsx.snap new file mode 100644 index 0000000000..1c5d8771b5 --- /dev/null +++ b/__tests__/shared/components/Settings/Preferences/Email/__snapshots__/index.jsx.snap @@ -0,0 +1,70 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders email preferences setting page correctly 1`] = ` +
    +

    + E-Mail Preferences +

    +
    + Your preferences +
    +
    + Challenge Pipeline page." + value="Pipeline" + /> + Gig Work page." + value="TaaS" + /> + + + + Topcoder Open you should definitely be subscribing to this one. Expect an update in your mailbox every Tuesday!" + value="TCO Tuesdays" + /> +
    +
    +`; diff --git a/__tests__/shared/components/Settings/Preferences/Email/index.jsx b/__tests__/shared/components/Settings/Preferences/Email/index.jsx new file mode 100644 index 0000000000..33164eba85 --- /dev/null +++ b/__tests__/shared/components/Settings/Preferences/Email/index.jsx @@ -0,0 +1,11 @@ +import React from 'react'; +import Renderer from 'react-test-renderer/shallow'; + +import Email from 'components/Settings/Preferences/Email'; + +const rnd = new Renderer(); + +it('renders email preferences setting page correctly', () => { + rnd.render(()); + expect(rnd.getRenderOutput()).toMatchSnapshot(); +}); \ No newline at end of file diff --git a/__tests__/shared/components/Settings/Preferences/__snapshots__/index.jsx.snap b/__tests__/shared/components/Settings/Preferences/__snapshots__/index.jsx.snap deleted file mode 100644 index c4e74aa901..0000000000 --- a/__tests__/shared/components/Settings/Preferences/__snapshots__/index.jsx.snap +++ /dev/null @@ -1,50 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`renders preferences setting page correctly 1`] = ` -
    -
    - , - "forum": , - "payment": , - } - } - names={ - Array [ - "e-mail", - "forum", - "payment", - ] - } - toggle={[Function]} - /> -
    -
    - - - -
    -
    -`; diff --git a/__tests__/shared/components/Settings/Preferences/index.jsx b/__tests__/shared/components/Settings/Preferences/index.jsx deleted file mode 100644 index c8c9042eed..0000000000 --- a/__tests__/shared/components/Settings/Preferences/index.jsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import Renderer from 'react-test-renderer/shallow'; - -import Preferences from 'components/Settings/Preferences'; - -const rnd = new Renderer(); - -it('renders preferences setting page correctly', () => { - rnd.render(()); - expect(rnd.getRenderOutput()).toMatchSnapshot(); -}); diff --git a/config/default.js b/config/default.js index 80ecfe7eea..ac784153ff 100644 --- a/config/default.js +++ b/config/default.js @@ -71,6 +71,7 @@ module.exports = { /* Holds params to signup for different newsletters. */ NEWSLETTER_SIGNUP: { + DEFAUL_LIST_ID: '28bfd3c062', COGNITIVE: { APIKEY: '', URL: '', diff --git a/src/shared/actions/newsletterPreferences.js b/src/shared/actions/newsletterPreferences.js new file mode 100644 index 0000000000..40a415c34a --- /dev/null +++ b/src/shared/actions/newsletterPreferences.js @@ -0,0 +1,104 @@ +/** + * Actions for the Newsletter preference container. + */ + +/* global fetch */ +import _ from 'lodash'; +import { createActions } from 'redux-actions'; +import { config } from 'topcoder-react-utils'; + +const PROXY_ENDPOINT = `${config.URL.COMMUNITY_APP}/api/mailchimp`; + +// Fetching member's newsletter preferences init +function fetchDataInit(email) { + return email; +} + +// Fetching member's newsletter preferences +async function fetchDataDone(emailHash, listId = config.NEWSLETTER_SIGNUP.DEFAUL_LIST_ID) { + /* NOTE: In the real life in most cases you don't want to use fetch() directly + * in an action. You want to create a service for your calls and use it here. + * However, in this example, to keep it a bit more compact, we use fetch() + * directly here. */ + try { + let error = false; + const subs = await fetch(`${PROXY_ENDPOINT}/${listId}/members/${emailHash}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }) + .then((result) => { + if (result.status !== 200) error = true; + return result.json(); + }); + + return { + email: emailHash, + preferences: subs.tags, + error, + }; + } catch (error) { + return { + email: emailHash, + error, + }; + } +} + +// Updates member newsletter subscription +async function updateSubscriptionsDone( + emailHash, tagId, status, listId = config.NEWSLETTER_SIGNUP.DEFAUL_LIST_ID, +) { + /* NOTE: In the real life in most cases you don't want to use fetch() directly + * in an action. You want to create a service for your calls and use it here. + * However, in this example, to keep it a bit more compact, we use fetch() + * directly here. */ + try { + let error = false; + const fetchUrl = `${PROXY_ENDPOINT}/${listId}/members/${emailHash}/tags`; + + const data = { + tags: [ + { name: tagId, status: status ? 'active' : 'inactive' }, + ], + }; + + const formData = JSON.stringify(data); + // use proxy for avoid 'Access-Control-Allow-Origin' bug + await fetch(fetchUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: formData, + }) + .then((result) => { + if (!result.ok) error = true; + return result.json(); + }); + + return { + id: tagId, + checked: status, + email: emailHash, + error, + }; + } catch (error) { + return { + id: tagId, + checked: status, + email: emailHash, + error, + }; + } +} + +export default createActions({ + NEWSLETTER_PREFERENCES: { + FETCH_DATA_INIT: fetchDataInit, + FETCH_DATA_DONE: fetchDataDone, + UPDATE_TAG_INIT: _.identity, + UPDATE_TAG_DONE: updateSubscriptionsDone, + }, +}); diff --git a/src/shared/components/Settings/Preferences/Email/index.jsx b/src/shared/components/Settings/Preferences/Email/index.jsx index b8eae9fa7b..521bae1b38 100644 --- a/src/shared/components/Settings/Preferences/Email/index.jsx +++ b/src/shared/components/Settings/Preferences/Email/index.jsx @@ -1,95 +1,141 @@ /** * Email Preferences component. */ -import { debounce, isEqual } from 'lodash'; +import { debounce, map } from 'lodash'; import React from 'react'; import PT from 'prop-types'; +import { toastr } from 'react-redux-toastr'; -import ConsentComponent from 'components/Settings/ConsentComponent'; +import ToggleableItem from 'components/Settings/ToggleableItem'; import './styles.scss'; +function toastrSuccess(title, message) { + setImmediate(() => { + toastr.success(title, message); + }); +} + +function toastrError(title, message) { + setImmediate(() => { + toastr.error(title, message); + }); +} + const SAVE_DELAY = 1000; -export default class EmailPreferences extends ConsentComponent { - saveEmailPreferences = debounce(() => { - const { - profile, - saveEmailPreferences, - tokenV3, - } = this.props; - const { emailPreferences } = this.state; +const newsletters = [ + { + id: 'Pipeline', + name: 'Challenge Pipeline', + desc: 'Subscribe to this newsletter if you want to get updates on the types of challenges coming up in the future. To view these challenges at your leisure you can always visit the Challenge Pipeline page.', + }, + { + id: 'TaaS', + name: 'Gig Work', + desc: 'This newsletter gets sent out at various times, specifically when we have an opportunity of mass appeal. For more information you can visit the Gig Work page.', + }, + { + id: 'Monthly Newsletter', + name: 'Monthly Newsletter', + desc: 'This newsletter gets sent out at the end of every month and contains a variety of important information across all of our tracks.', + }, + { + id: 'Marathon Match Reminders', + name: 'Marathon Match Reminders', + desc: 'Receive updates whenever a new marathon match is scheduled.', + }, + { + id: 'Single Round Match Reminders', + name: 'Single Round Match (SRM) Reminders', + desc: 'Attention Competitive Programmers! If there is any newsletter you are subscribing too, it better be this one. Receive updates when a new SRM event is scheduled.', + }, + { + id: 'TCO Tuesdays', + name: 'TCO Newsletter', + desc: 'For all the latest updates surrounding the Topcoder Open you should definitely be subscribing to this one. Expect an update in your mailbox every Tuesday!', + }, +]; - saveEmailPreferences( - profile, - tokenV3, - emailPreferences, - ); +export default class EmailPreferences extends React.Component { + saveEmailPreferences = debounce((id, checked) => { + const { email, saveEmailPreferences } = this.props; + saveEmailPreferences(email, id, checked); }, SAVE_DELAY); constructor(props) { super(props); this.state = { - emailPreferences: {}, + emailPreferences: { ...props.preferences }, }; - this.onHandleChange = this.onHandleChange.bind(this); this.onChange = this.onChange.bind(this); - this.populate = this.populate.bind(this); - } - - componentDidMount() { - const { profileState: { emailPreferences } } = this.props; - if (emailPreferences) this.populate(emailPreferences); } - componentWillReceiveProps(nextProps) { - const { profileState: { emailPreferences } } = nextProps; - if (emailPreferences && !isEqual(this.state.emailPreferences, emailPreferences)) { - this.populate(emailPreferences); - } - } + componentDidUpdate(prevProps) { + const { updated } = this.props; + if (updated && updated !== prevProps.updated) { + if (updated.error) { + toastrError('Error! ', 'Failed to update Your email preferences :-('); + } + const { emailPreferences } = this.state; + const { id, checked } = updated; + if (!emailPreferences[id]) emailPreferences[id] = { id, checked }; + else emailPreferences[id].checked = checked; - shouldComponentUpdate(nextProps) { - const { profileState: { emailPreferences } } = nextProps; - if (emailPreferences && this.props.profileState.emailPreferences !== emailPreferences) { - return true; + // eslint-disable-next-line react/no-did-update-set-state + this.setState({ + emailPreferences, + }); + toastrSuccess('Success! ', 'Your email preferences were updated.'); } - return false; - } - - onHandleChange(id, checked) { - this.showConsent(this.onChange.bind(this, id, checked)); } onChange(id, checked) { document.querySelectorAll(`#pre-onoffswitch-${id}`).forEach((el) => { el.checked = checked; }); // eslint-disable-line no-param-reassign - const { emailPreferences } = this.state; - emailPreferences[id] = checked; - this.setState({ - emailPreferences, - }, () => this.saveEmailPreferences()); - } - - populate(data) { - this.setState({ - emailPreferences: { ...data }, - }); + this.saveEmailPreferences(id, checked); } render() { + const { emailPreferences } = this.state; return (

    E-Mail Preferences

    +
    + Your preferences +
    +
    + { + map(newsletters, (newsletter) => { + const checked = emailPreferences[newsletter.id] + ? emailPreferences[newsletter.id].checked : false; + return ( + this.onChange(newsletter.id, e.target.checked)} + /> + ); + }) + } +
    ); } } +EmailPreferences.defaultProps = { + updated: null, +}; + EmailPreferences.propTypes = { - tokenV3: PT.string.isRequired, - profile: PT.shape().isRequired, - profileState: PT.shape().isRequired, + email: PT.string.isRequired, + preferences: PT.shape().isRequired, saveEmailPreferences: PT.func.isRequired, + updated: PT.shape(), }; diff --git a/src/shared/components/Settings/Preferences/index.jsx b/src/shared/components/Settings/Preferences/index.jsx index 18ee9f971f..5db18e1be9 100644 --- a/src/shared/components/Settings/Preferences/index.jsx +++ b/src/shared/components/Settings/Preferences/index.jsx @@ -15,7 +15,7 @@ import Forum from 'assets/images/preferences/forum.svg'; import Payment from 'assets/images/preferences/payment.svg'; import SideBar from 'components/Settings/SideBar'; import ErrorWrapper from 'components/Settings/ErrorWrapper'; -import Email from './Email'; +import NewsletterPreferencesContainer from 'containers/NewsletterPreferences'; import { SCREEN_SIZE } from '../constants'; import './styles.scss'; @@ -83,9 +83,10 @@ export default class Preferences extends React.Component { } renderTabContent(tab) { + const { profile: { email } } = this.props; switch (tab) { case 'e-mail': - return ; + return ; case 'forum': return (window.location.href = `${config.URL.FORUMS}/?module=Settings`) && ; case 'payment': @@ -140,4 +141,5 @@ export default class Preferences extends React.Component { Preferences.propTypes = { clearToastrNotification: PT.func.isRequired, + profile: PT.shape().isRequired, }; diff --git a/src/shared/components/Settings/ToggleableItem/index.jsx b/src/shared/components/Settings/ToggleableItem/index.jsx index 1728b62d44..bbfe110863 100644 --- a/src/shared/components/Settings/ToggleableItem/index.jsx +++ b/src/shared/components/Settings/ToggleableItem/index.jsx @@ -7,6 +7,7 @@ */ import React from 'react'; import PT from 'prop-types'; +import ReactHtmlParser from 'react-html-parser'; import './styles.scss'; @@ -25,7 +26,7 @@ export default function ToggleableItem({ {primaryText}

    - {secondaryText} + {ReactHtmlParser(secondaryText)}

    diff --git a/src/shared/containers/NewsletterPreferences.jsx b/src/shared/containers/NewsletterPreferences.jsx new file mode 100644 index 0000000000..acde23fc44 --- /dev/null +++ b/src/shared/containers/NewsletterPreferences.jsx @@ -0,0 +1,88 @@ + +import React from 'react'; +import PT from 'prop-types'; +import { connect } from 'react-redux'; +import actions from 'actions/newsletterPreferences'; +import LoadingIndicator from 'components/LoadingIndicator'; +import Email from 'components/Settings/Preferences/Email'; + +class NewsletterPreferencesContainer extends React.Component { + componentDidMount() { + const { + loading, + email, + getNewsletterPreferencesDone, + } = this.props; + if (!loading) { + getNewsletterPreferencesDone(email); + } + } + + render() { + const { + loading, error, preferences, saveEmailPreferences, email, updated, + } = this.props; + if (loading || !preferences) return ; + if (error) { + return Error loading Newsletter Preferences :-( {error.message}; + } + return ( + + ); + } +} + +NewsletterPreferencesContainer.defaultProps = { + loading: false, + error: null, + preferences: null, + updated: null, +}; + +NewsletterPreferencesContainer.propTypes = { + loading: PT.bool, + getNewsletterPreferencesDone: PT.func.isRequired, + saveEmailPreferences: PT.func.isRequired, + error: PT.bool, + preferences: PT.shape(), + email: PT.string.isRequired, + updated: PT.shape(), +}; + +function mapStateToProps(state) { + const { newsletterPreferences } = state; + if (newsletterPreferences) { + return { + loading: newsletterPreferences.loading, + error: newsletterPreferences.error, + preferences: newsletterPreferences.preferences, + updated: newsletterPreferences.updated, + }; + } + + return state; +} + +function mapDispatchToProps(dispatch) { + return { + getNewsletterPreferencesDone: (email) => { + dispatch(actions.newsletterPreferences.fetchDataDone(email)); + }, + saveEmailPreferences: (email, id, checked) => { + dispatch(actions.newsletterPreferences.updateTagInit()); + dispatch(actions.newsletterPreferences.updateTagDone(email, id, checked)); + }, + }; +} + +const Container = connect( + mapStateToProps, + mapDispatchToProps, +)(NewsletterPreferencesContainer); + +export default Container; diff --git a/src/shared/containers/Settings.jsx b/src/shared/containers/Settings.jsx index 75731d4247..40009cef92 100644 --- a/src/shared/containers/Settings.jsx +++ b/src/shared/containers/Settings.jsx @@ -103,7 +103,6 @@ SettingsContainer.propTypes = { deleteWebLink: PT.func.isRequired, linkExternalAccount: PT.func.isRequired, unlinkExternalAccount: PT.func.isRequired, - saveEmailPreferences: PT.func.isRequired, updatePassword: PT.func.isRequired, settingsTab: PT.string, authenticating: PT.bool.isRequired, @@ -155,7 +154,8 @@ function mapDispatchToProps(dispatch) { dispatch(lookupActions.getCountriesInit()); dispatch(lookupActions.getCountriesDone()); } else if (settingsTab === TABS.PREFERENCES) { - dispatch(profileActions.getEmailPreferencesDone(profile, tokenV3)); + // Deprecated. Leaving it here as reminder to update topcoder-react-lib as well + // dispatch(profileActions.getEmailPreferencesDone(profile, tokenV3)); } else if (settingsTab === TABS.ACCOUNT) { dispatch(profileActions.getLinkedAccountsDone(profile, tokenV3)); dispatch(profileActions.getExternalLinksDone(handle)); @@ -232,9 +232,6 @@ function mapDispatchToProps(dispatch) { dispatch(profileActions.unlinkExternalAccountInit({ providerType })); dispatch(profileActions.unlinkExternalAccountDone(profile, tokenV3, providerType)); }, - saveEmailPreferences: (profile, tokenV3, preferences) => { - dispatch(profileActions.saveEmailPreferencesDone(profile, tokenV3, preferences)); - }, updatePassword: (profile, tokenV3, newPassword, oldPassword) => { dispatch(profileActions.updatePasswordInit()); dispatch(profileActions.updatePasswordDone(profile, tokenV3, newPassword, oldPassword)); diff --git a/src/shared/reducers/index.js b/src/shared/reducers/index.js index 6b759c0c7c..8e417d6602 100644 --- a/src/shared/reducers/index.js +++ b/src/shared/reducers/index.js @@ -34,6 +34,7 @@ import { factory as tcCommunitiesFactory } from './tc-communities'; import { factory as leaderboardFactory } from './leaderboard'; import { factory as scoreboardFactory } from './tco/scoreboard'; import { factory as termsFactory } from './terms'; +import newsletterPreferences from './newsletterPreferences'; /** * Given HTTP request, generates options for SSR by topcoder-react-lib's reducer @@ -136,6 +137,7 @@ export function factory(req) { newsletterArchive, menuNavigation, challengesBlock, + newsletterPreferences, })); } diff --git a/src/shared/reducers/newsletterPreferences.js b/src/shared/reducers/newsletterPreferences.js new file mode 100644 index 0000000000..f1652090f2 --- /dev/null +++ b/src/shared/reducers/newsletterPreferences.js @@ -0,0 +1,74 @@ +/** + * Reducer for state.newsletterPreferences + */ + +import actions from 'actions/newsletterPreferences'; +import { handleActions } from 'redux-actions'; + +/** + * Handles newsletterPreferences.fetchDataInit action. + * @param {Object} state Previous state. + * @param {Object} name The action. + */ +function onInit(state, { payload }) { + return { + ...state, + email: payload, + preferences: [], + loading: true, + }; +} + +/** + * Handles newsletterPreferences.fetchDataDone action. + * @param {Object} state Previous state. + * @param {Object} action The action. + */ +function onDone(state, { payload }) { + const preferences = {}; + if (payload.preferences) { + payload.preferences.forEach((record) => { + preferences[record.name] = { ...record, checked: true }; + }); + } + return { + ...state, + preferences: payload.error ? null : preferences, + error: payload.error, + loading: false, + }; +} + +function onUpdateTagInit(state) { + return { + ...state, + updated: null, + }; +} + +function onUpdateTagDone(state, { payload }) { + // eslint-disable-next-line no-param-reassign + state.preferences[payload.id] = { name: payload.id, checked: payload.checked }; + return { + ...state, + updated: payload, + }; +} + +/** + * Creates newsletterPreferences reducer with the specified initial state. + * @param {Object} state Optional. If not given, the default one is + * generated automatically. + * @return {Function} Reducer. + */ +function create(state = {}) { + return handleActions({ + [actions.newsletterPreferences.fetchDataInit]: onInit, + [actions.newsletterPreferences.fetchDataDone]: onDone, + [actions.newsletterPreferences.updateTagInit]: onUpdateTagInit, + [actions.newsletterPreferences.updateTagDone]: onUpdateTagDone, + }, state); +} + +/* Reducer with the default initial state. */ +export default create(); From 278961a43cc1f7e734d69fdaa083b2d52ddf1ad1 Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Fri, 5 Jun 2020 08:54:06 +0300 Subject: [PATCH 5/9] Update config.yml --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 10da5c46c7..d5366e02ad 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -258,7 +258,7 @@ workflows: filters: branches: only: - - staging-env-setup + - feature-contentful # Production builds are exectuted # when PR is merged to the master # Don't change anything in this configuration From d50ce4bf074786469a24f5dd6f586b695dffdec4 Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Fri, 5 Jun 2020 09:45:14 +0300 Subject: [PATCH 6/9] Fixed tests --- .../shared/components/Settings/Preferences/Email/index.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/__tests__/shared/components/Settings/Preferences/Email/index.jsx b/__tests__/shared/components/Settings/Preferences/Email/index.jsx index 33164eba85..f49efaae8d 100644 --- a/__tests__/shared/components/Settings/Preferences/Email/index.jsx +++ b/__tests__/shared/components/Settings/Preferences/Email/index.jsx @@ -6,6 +6,6 @@ import Email from 'components/Settings/Preferences/Email'; const rnd = new Renderer(); it('renders email preferences setting page correctly', () => { - rnd.render(()); + rnd.render(()); expect(rnd.getRenderOutput()).toMatchSnapshot(); -}); \ No newline at end of file +}); From 1dcad20573952418263485954406f09ef4dc70f2 Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Fri, 5 Jun 2020 15:17:52 +0300 Subject: [PATCH 7/9] Update config.yml --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index d5366e02ad..c5f16f1781 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -238,6 +238,7 @@ workflows: branches: only: - develop + - feature-contentful # This is alternate dev env for parallel testing - "build-qa": context : org-global From a3676b5ea5d6c242d1d4e1eec8cf7af72ee560f1 Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Fri, 5 Jun 2020 15:19:37 +0300 Subject: [PATCH 8/9] Update config.yml --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c5f16f1781..3af2326db1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -231,6 +231,7 @@ workflows: branches: only: - develop + - feature-contentful # This is alternate dev env for parallel testing - "build-test": context : org-global @@ -238,7 +239,6 @@ workflows: branches: only: - develop - - feature-contentful # This is alternate dev env for parallel testing - "build-qa": context : org-global From d6ead47007b30067a535414adcaaf8927a3b7f24 Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Thu, 11 Jun 2020 12:43:08 +0300 Subject: [PATCH 9/9] Update `taas` to `Gig Work` tag for MailChimp --- .../Settings/Preferences/Email/__snapshots__/index.jsx.snap | 4 ++-- src/shared/components/Settings/Preferences/Email/index.jsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/__tests__/shared/components/Settings/Preferences/Email/__snapshots__/index.jsx.snap b/__tests__/shared/components/Settings/Preferences/Email/__snapshots__/index.jsx.snap index 1c5d8771b5..cde7b4e722 100644 --- a/__tests__/shared/components/Settings/Preferences/Email/__snapshots__/index.jsx.snap +++ b/__tests__/shared/components/Settings/Preferences/Email/__snapshots__/index.jsx.snap @@ -27,11 +27,11 @@ exports[`renders email preferences setting page correctly 1`] = ` /> Gig Work page." - value="TaaS" + value="Gig Work" /> Challenge Pipeline page.', }, { - id: 'TaaS', + id: 'Gig Work', name: 'Gig Work', desc: 'This newsletter gets sent out at various times, specifically when we have an opportunity of mass appeal. For more information you can visit the Gig Work page.', },