Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const DEFAULT_FREE_LEXICAL_CONTENT = '{"root":{"children":[{"children":[{"detail
const DEFAULT_PAID_LEXICAL_CONTENT = '{"root":{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Welcome, and thank you for your support — it means a lot.","type":"extended-text","version":1}],"direction":"ltr","format":"","indent":0,"type":"paragraph","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"As a paid member, you now have full access to everything: the complete archive, and any paid-only content going forward. New posts will land straight to your inbox, and you can log in any time to ","type":"extended-text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"catch up","type":"extended-text","version":1}],"direction":"ltr","format":"","indent":0,"type":"link","version":1,"rel":"noreferrer","target":null,"title":null,"url":"__GHOST_URL__/"},{"detail":0,"format":0,"mode":"normal","style":"","text":" on anything you\'ve missed.","type":"extended-text","version":1}],"direction":"ltr","format":"","indent":0,"type":"paragraph","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"A little housekeeping: If this email landed in spam or promotions, try moving it to your primary inbox and adding this address to your contacts. Small signals like that help your inbox recognize that these messages matter to you.","type":"extended-text","version":1}],"direction":"ltr","format":"","indent":0,"type":"paragraph","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Have questions or just want to say hi? Feel free to reply directly to this email or any newsletter in the future.","type":"extended-text","version":1}],"direction":"ltr","format":"","indent":0,"type":"paragraph","version":1}],"direction":"ltr","format":"","indent":0,"type":"root","version":1}}';

export const getDefaultWelcomeEmailValues = (emailType: WelcomeEmailType, siteTitle: string | null | undefined) => ({
name: emailType === 'free' ? 'Welcome Email (Free)' : 'Welcome Email (Paid)',
name: emailType === 'free' ? 'Free member welcome flow' : 'Paid member welcome flow',
slug: WELCOME_EMAIL_SLUGS[emailType],
subject: emailType === 'free'
? `Welcome to ${siteTitle || 'our site'}`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const automatedEmailsFixture = {
automated_emails: [{
id: 'free-welcome-email-id',
status: 'active',
name: 'Welcome Email (Free)',
name: 'Free member welcome flow',
slug: 'member-welcome-email-free',
subject: 'Welcome to Test Site',
// Note the lexical content includes a template token ({name})
Expand Down Expand Up @@ -1016,7 +1016,7 @@ test.describe('Member emails settings', async () => {
automated_emails: [{
id: 'paid-welcome-email-id',
status: 'inactive',
name: 'Welcome Email (Paid)',
name: 'Paid member welcome flow',
slug: 'member-welcome-email-paid',
subject: 'Welcome to your paid subscription',
lexical: '{"root":{"children":[]}}',
Expand Down Expand Up @@ -1221,7 +1221,7 @@ test.describe('Member emails settings', async () => {
automated_emails: [{
id: 'paid-welcome-email-id',
status: 'inactive',
name: 'Welcome Email (Paid)',
name: 'Paid member welcome flow',
slug: 'member-welcome-email-paid',
subject: 'Welcome to your paid subscription',
lexical: '{"root":{"children":[]}}',
Expand Down Expand Up @@ -1363,7 +1363,7 @@ test.describe('Member emails settings', async () => {
automated_emails: [{
id: 'new-free-welcome-email-id',
status: 'inactive',
name: 'Welcome Email (Free)',
name: 'Free member welcome flow',
slug: 'member-welcome-email-free',
subject: 'Welcome to Test Site',
lexical: '{"root":{"children":[]}}',
Expand Down Expand Up @@ -1411,7 +1411,7 @@ test.describe('Member emails settings', async () => {
automated_emails: [{
id: 'free-welcome-email-id',
status: 'inactive',
name: 'Welcome Email (Free)',
name: 'Free member welcome flow',
slug: 'member-welcome-email-free',
subject: 'Welcome to Test Site',
lexical: '{"root":{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Welcome!","type":"extended-text","version":1}],"direction":"ltr","format":"","indent":0,"type":"paragraph","version":1}],"direction":"ltr","format":"","indent":0,"type":"root","version":1}}',
Expand Down Expand Up @@ -1458,7 +1458,7 @@ test.describe('Member emails settings', async () => {
automated_emails: [{
id: 'new-free-welcome-email-id',
status: 'active',
name: 'Welcome Email (Free)',
name: 'Free member welcome flow',
slug: 'member-welcome-email-free',
subject: 'Welcome to Test Site',
lexical: '{"root":{"children":[]}}',
Expand Down Expand Up @@ -1502,7 +1502,7 @@ test.describe('Member emails settings', async () => {
automated_emails: [{
id: 'free-welcome-email-id',
status: 'inactive',
name: 'Welcome Email (Free)',
name: 'Free member welcome flow',
slug: 'member-welcome-email-free',
subject: 'Welcome to Test Site',
lexical: '{"root":{"children":[]}}',
Expand Down Expand Up @@ -1553,7 +1553,7 @@ test.describe('Member emails settings', async () => {
automated_emails: [{
id: 'free-welcome-email-id',
status: 'active',
name: 'Welcome Email (Free)',
name: 'Free member welcome flow',
slug: 'member-welcome-email-free',
subject: 'Welcome to Test Site',
lexical: '{"root":{"children":[]}}',
Expand Down
2 changes: 1 addition & 1 deletion apps/portal/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@tryghost/portal",
"version": "2.69.5",
"version": "2.69.6",
"license": "MIT",
"repository": "https://github.com/TryGhost/Ghost",
"author": "Ghost Foundation",
Expand Down
7 changes: 5 additions & 2 deletions apps/portal/src/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -558,8 +558,8 @@ async function showPopupNotification({data, state}) {

async function updateNewsletterPreference({data, state, api}) {
try {
const {newsletters, enableCommentNotifications} = data;
if (!newsletters && enableCommentNotifications === undefined) {
const {newsletters, enableCommentNotifications, enableUpdatesAndAnnouncements} = data;
if (!newsletters && enableCommentNotifications === undefined && enableUpdatesAndAnnouncements === undefined) {
return {};
}
const updateData = {};
Expand All @@ -569,6 +569,9 @@ async function updateNewsletterPreference({data, state, api}) {
if (enableCommentNotifications !== undefined) {
updateData.enableCommentNotifications = enableCommentNotifications;
}
if (enableUpdatesAndAnnouncements !== undefined) {
updateData.enableUpdatesAndAnnouncements = enableUpdatesAndAnnouncements;
}
const member = await api.member.update(updateData);
const action = 'updateNewsletterPref:success';
return {
Expand Down
3 changes: 2 additions & 1 deletion apps/portal/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,8 @@ export default class App extends React.Component {
uuid: qParams.get('uuid'),
key: qParams.get('key'),
newsletterUuid: qParams.get('newsletter'),
comments: qParams.get('comments')
comments: qParams.get('comments'),
updatesAndAnnouncements: qParams.get('updatesandannouncements')
}
};
} else { // any malformed unsubscribe links should simply go to email prefs
Expand Down
65 changes: 61 additions & 4 deletions apps/portal/src/components/common/newsletter-management.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import AppContext from '../../app-context';
import CloseButton from './close-button';
import BackButton from './back-button';
import {useContext} from 'react';
import {useContext, useState, useRef} from 'react';
import Switch from './switch';
import {getSiteNewsletters, hasMemberGotEmailSuppression} from '../../utils/helpers';
import ActionButton from './action-button';
Expand Down Expand Up @@ -78,6 +78,40 @@ function CommentsSection({updateCommentNotifications, isCommentsEnabled, enableC
);
}

function UpdatesAndAnnouncementsSection({updateUpdatesAndAnnouncements, canChangeUpdatesAndAnnouncements, enableUpdatesAndAnnouncements}) {
const {doAction, site} = useContext(AppContext);
const [isUpdating, setIsUpdating] = useState(false);

if (!canChangeUpdatesAndAnnouncements) {
return null;
}

const handleToggle = async (e, checked) => {
setIsUpdating(true);
try {
await updateUpdatesAndAnnouncements(checked);
doAction('showPopupNotification', {
action: 'updated:success',
message: t('Email preferences updated.')
});
} finally {
setIsUpdating(false);
}
};

return (
<section className='gh-portal-list-toggle-wrapper' data-testid="updates-and-announcements-toggle">
<div className='gh-portal-list-detail'>
<h3>{t('Updates & announcements')}</h3>
<p>{t('Occasional updates from {siteTitle}', {siteTitle: site?.title})}</p>
</div>
<div style={{display: 'flex', alignItems: 'center'}}>
<Switch id="updates-and-announcements" onToggle={handleToggle} checked={enableUpdatesAndAnnouncements} disabled={isUpdating} dataTestId="switch-input" />
</div>
</section>
);
}

function NewsletterPrefs({subscribedNewsletters, setSubscribedNewsletters, hasNewslettersEnabled}) {
const {site} = useContext(AppContext);
const newsletters = getSiteNewsletters({site});
Expand Down Expand Up @@ -111,13 +145,27 @@ export default function NewsletterManagement({
subscribedNewsletters,
updateSubscribedNewsletters,
updateCommentNotifications,
updateUpdatesAndAnnouncements,
unsubscribeAll,
isPaidMember,
isCommentsEnabled,
enableCommentNotifications
enableCommentNotifications,
canChangeUpdatesAndAnnouncements,
enableUpdatesAndAnnouncements
}) {
const {brandColor, doAction, member, site} = useContext(AppContext);
const isDisabled = !subscribedNewsletters?.length && ((isCommentsEnabled && !enableCommentNotifications) || !isCommentsEnabled);

// Snapshot the updates & announcements value when the modal opens. When the member has no
// explicit preference yet (null), derive it from whether any newsletter is subscribed at open
// time and keep it fixed while open, so toggling a newsletter doesn't also appear to flip
// updates & announcements.
const wasInitiallySubscribedToAnyNewsletters = useRef(!!subscribedNewsletters?.length).current;
const hasExplicitUpdatesPreference = enableUpdatesAndAnnouncements !== null && enableUpdatesAndAnnouncements !== undefined;
const effectiveEnableUpdatesAndAnnouncements = hasExplicitUpdatesPreference ? enableUpdatesAndAnnouncements : wasInitiallySubscribedToAnyNewsletters;

const hasNoCommentSubscription = (isCommentsEnabled && !enableCommentNotifications) || !isCommentsEnabled;
const hasNoUpdatesSubscription = (canChangeUpdatesAndAnnouncements && !effectiveEnableUpdatesAndAnnouncements) || !canChangeUpdatesAndAnnouncements;
const isDisabled = !subscribedNewsletters?.length && hasNoCommentSubscription && hasNoUpdatesSubscription;
const EmptyNotification = () => {
return null;
};
Expand All @@ -138,14 +186,23 @@ export default function NewsletterManagement({
id: d.id
};
});
updateSubscribedNewsletters(newsletters);
if (canChangeUpdatesAndAnnouncements && !hasExplicitUpdatesPreference) {
updateSubscribedNewsletters(newsletters, effectiveEnableUpdatesAndAnnouncements);
} else {
updateSubscribedNewsletters(newsletters);
}
}}
/>
<CommentsSection
isCommentsEnabled={isCommentsEnabled}
enableCommentNotifications={enableCommentNotifications}
updateCommentNotifications={updateCommentNotifications}
/>
<UpdatesAndAnnouncementsSection
canChangeUpdatesAndAnnouncements={canChangeUpdatesAndAnnouncements}
enableUpdatesAndAnnouncements={effectiveEnableUpdatesAndAnnouncements}
updateUpdatesAndAnnouncements={updateUpdatesAndAnnouncements}
/>
</div>
</div>
<div className='gh-portal-btn-product gh-portal-btn-unsubscribe' style={{marginTop: '-48px', marginBottom: 0}}>
Expand Down
6 changes: 5 additions & 1 deletion apps/portal/src/components/common/switch.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export const SwitchStyles = `
}
`;

function Switch({id, label = '', onToggle, checked = false, dataTestId = 'switch-input'}) {
function Switch({id, label = '', onToggle, checked = false, disabled = false, dataTestId = 'switch-input'}) {
const [isChecked, setIsChecked] = useState(checked);

useEffect(() => {
Expand All @@ -99,11 +99,15 @@ function Switch({id, label = '', onToggle, checked = false, dataTestId = 'switch
ref={inputRef}
type="checkbox"
checked={isChecked}
disabled={disabled}
id={id}
onChange={() => {}}
aria-label={label}
/>
<span className="input-toggle-component" onClick={(e) => {
if (disabled) {
return;
}
setIsChecked(!isChecked);
onToggle(e, !isChecked);
}} data-testid={dataTestId}></span>
Expand Down
21 changes: 18 additions & 3 deletions apps/portal/src/components/pages/account-email-page.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,11 @@ export default function AccountEmailPage() {
const defaultSubscribedNewsletters = [...(member?.newsletters || [])];
const [subscribedNewsletters, setSubscribedNewsletters] = useState(defaultSubscribedNewsletters);
const {comments_enabled: commentsEnabled} = site;
const {enable_comment_notifications: enableCommentNotifications} = member || {};
const canChangeUpdatesAndAnnouncements = !!site.labs?.automations;
const {
enable_comment_notifications: enableCommentNotifications,
enable_updates_and_announcements: enableUpdatesAndAnnouncements
} = member || {};

useEffect(() => {
setSubscribedNewsletters(member?.newsletters || []);
Expand All @@ -92,9 +96,12 @@ export default function AccountEmailPage() {
hasNewslettersEnabled={hasNewslettersEnabled}
notification={newsletterUuid ? HeaderNotification : null}
subscribedNewsletters={subscribedNewsletters}
updateSubscribedNewsletters={(updatedNewsletters) => {
updateSubscribedNewsletters={(updatedNewsletters, enableUpdatesAndAnnouncementsValue) => {
setSubscribedNewsletters(updatedNewsletters);
doAction('updateNewsletterPreference', {newsletters: updatedNewsletters});
doAction('updateNewsletterPreference', {
newsletters: updatedNewsletters,
enableUpdatesAndAnnouncements: enableUpdatesAndAnnouncementsValue
});
doAction('showPopupNotification', {
action: 'updated:success',
message: t('Email preferences updated.')
Expand All @@ -103,6 +110,9 @@ export default function AccountEmailPage() {
updateCommentNotifications={async (enabled) => {
doAction('updateNewsletterPreference', {enableCommentNotifications: enabled});
}}
updateUpdatesAndAnnouncements={async (enabled) => {
doAction('updateNewsletterPreference', {enableUpdatesAndAnnouncements: enabled});
}}
unsubscribeAll={() => {
setSubscribedNewsletters([]);
doAction('showPopupNotification', {
Expand All @@ -113,11 +123,16 @@ export default function AccountEmailPage() {
if (commentsEnabled) {
data.enableCommentNotifications = false;
}
if (canChangeUpdatesAndAnnouncements) {
data.enableUpdatesAndAnnouncements = false;
}
doAction('updateNewsletterPreference', data);
}}
isPaidMember={isPaidMember({member})}
isCommentsEnabled={commentsEnabled !== 'off'}
enableCommentNotifications={enableCommentNotifications}
canChangeUpdatesAndAnnouncements={canChangeUpdatesAndAnnouncements}
enableUpdatesAndAnnouncements={enableUpdatesAndAnnouncements}
/>
);
}
Loading