diff --git a/ghost/portal/src/App.js b/ghost/portal/src/App.js index 330b5d252a..247eca8451 100644 --- a/ghost/portal/src/App.js +++ b/ghost/portal/src/App.js @@ -19,7 +19,7 @@ const DEV_MODE_DATA = { showPopup: true, site: Fixtures.site, member: Fixtures.member.free, - page: 'signup', + page: 'accountEmail', ...Fixtures.paidMemberOnTier(), pageData: Fixtures.offer }; @@ -393,6 +393,16 @@ export default class App extends React.Component { /** Fetch state from Portal Links */ fetchLinkData() { + const qParams = new URLSearchParams(window.location.search); + if (qParams.get('uuid') && qParams.get('action') === 'unsubscribe') { + return { + showPopup: true, + page: 'unsubscribe', + pageData: { + uuid: qParams.get('uuid') + } + }; + } const productMonthlyPriceQueryRegex = /^(?:(\w+?))?\/monthly$/; const productYearlyPriceQueryRegex = /^(?:(\w+?))?\/yearly$/; const offersRegex = /^offers\/(\w+?)\/?$/; diff --git a/ghost/portal/src/components/pages/UnsubscribePage.js b/ghost/portal/src/components/pages/UnsubscribePage.js new file mode 100644 index 0000000000..49c7af4508 --- /dev/null +++ b/ghost/portal/src/components/pages/UnsubscribePage.js @@ -0,0 +1,85 @@ +import AppContext from '../../AppContext'; + +import {useContext, useEffect, useState} from 'react'; +import {getSiteNewsletters} from '../../utils/helpers'; +import setupGhostApi from '../../utils/api'; +import NewsletterManagement from '../common/NewsletterManagement'; + +const React = require('react'); + +function AccountHeader() { + return ( +
+

Email preferences

+
+ ); +} + +export default function UnsubscribePage() { + const {site, pageData, onAction} = useContext(AppContext); + const api = setupGhostApi({siteUrl: site.url}); + const [member, setMember] = useState(); + const siteNewsletters = getSiteNewsletters({site}); + const defaultNewsletters = siteNewsletters.filter((d) => { + return d.subscribe_on_signup; + }); + const [subscribedNewsletters, setSubscribedNewsletters] = useState(defaultNewsletters); + + useEffect(() => { + const ghostApi = setupGhostApi({siteUrl: site.url}); + (async () => { + const memberData = await ghostApi.member.newsletters({uuid: pageData.uuid}); + + setMember(memberData); + setSubscribedNewsletters(memberData?.newsletters || []); + })(); + }, [pageData.uuid, site.url]); + + // Case: Email not found + if (member === null) { + return ( +
+ +

+

Unsubscribe Failed

+

+
+

Email address not found.

+
+
+ ); + } + + // Case: Single active newsletter + if (siteNewsletters?.length === 1) { + return ( +
+ +

+

Successfully unsubscribed

+

+
+

{member?.email} will no longer receive this newsletter.

+
+
+ ); + } + return ( + { + setSubscribedNewsletters(newsletters); + await api.member.updateNewsletters({uuid: pageData.uuid, newsletters}); + }} + unsubscribeAll={async () => { + setSubscribedNewsletters([]); + onAction('showPopupNotification', { + action: 'updated:success', + message: `Newsletter preference updated.` + }); + await api.member.updateNewsletters({uuid: pageData.uuid, newsletters: []}); + }} + isPaidMember={member?.status !== 'free'} + /> + ); +} diff --git a/ghost/portal/src/pages.js b/ghost/portal/src/pages.js index 0595ed6d8a..ad109234c9 100644 --- a/ghost/portal/src/pages.js +++ b/ghost/portal/src/pages.js @@ -8,6 +8,7 @@ import AccountProfilePage from './components/pages/AccountProfilePage'; import AccountEmailPage from './components/pages/AccountEmailPage'; import OfferPage from './components/pages/OfferPage'; import NewsletterSelectionPage from './components/pages/NewsletterSelectionPage'; +import UnsubscribePage from './components/pages/UnsubscribePage'; /** List of all available pages in Portal, mapped to their UI component * Any new page added to portal needs to be mapped here @@ -20,6 +21,7 @@ const Pages = { accountProfile: AccountProfilePage, accountEmail: AccountEmailPage, signupNewsletter: NewsletterSelectionPage, + unsubscribe: UnsubscribePage, magiclink: MagicLinkPage, loading: LoadingPage, offer: OfferPage diff --git a/ghost/portal/src/utils/api.js b/ghost/portal/src/utils/api.js index aeb147e963..2d40fcf5b5 100644 --- a/ghost/portal/src/utils/api.js +++ b/ghost/portal/src/utils/api.js @@ -192,6 +192,43 @@ function setupGhostApi({siteUrl = window.location.origin}) { }); }, + async newsletters({uuid}) { + let url = endpointFor({type: 'members', resource: `member/newsletters`}); + url = url + `?uuid=${uuid}`; + return makeRequest({ + url, + credentials: 'same-origin' + }).then(function (res) { + if (!res.ok || res.status === 204) { + return null; + } + return res.json(); + }); + }, + + async updateNewsletters({uuid, newsletters}) { + let url = endpointFor({type: 'members', resource: `member/newsletters`}); + url = url + `?uuid=${uuid}`; + const body = { + newsletters + }; + + return makeRequest({ + url, + method: 'PUT', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(body) + }).then(function (res) { + if (res.ok) { + return res.json(); + } else { + throw new Error('Failed to upadte newsletter preferences'); + } + }); + }, + async updateEmailAddress({email}) { const identity = await api.member.identity(); const url = endpointFor({type: 'members', resource: 'member/email'});