From 9208fb9a3cf644e7dfe758e8d1bbb13cd03bf517 Mon Sep 17 00:00:00 2001 From: joostdecock Date: Tue, 2 Jan 2024 16:59:14 +0100 Subject: [PATCH] fix: Handle newsletter unsubscribe There were a few issues with the newsletter unsubscribe links that we sent out in the newsletter. They were pointing to the backend for one thing. Also updated the frontend pages to handle unsubscribe from both users and subscribers. --- scripts/newsletter-lib.mjs | 4 +- sites/backend/src/controllers/subscribers.mjs | 6 +-- sites/backend/src/models/subscriber.mjs | 39 +++++++++++++------ sites/backend/src/models/user.mjs | 1 + sites/backend/src/routes/subscribers.mjs | 2 +- sites/org/pages/newsletter/subscribe.mjs | 7 ++-- sites/org/pages/newsletter/unsubscribe.mjs | 19 ++++----- .../shared/components/account/newsletter.mjs | 13 ++++++- .../shared/components/wrappers/auth/index.mjs | 10 ++++- sites/shared/hooks/use-backend.mjs | 6 +-- 10 files changed, 71 insertions(+), 36 deletions(-) diff --git a/scripts/newsletter-lib.mjs b/scripts/newsletter-lib.mjs index 01eb2004fd5..4bcf0474382 100644 --- a/scripts/newsletter-lib.mjs +++ b/scripts/newsletter-lib.mjs @@ -115,7 +115,9 @@ const send = async (test = true) => { if (l > 0) { const body = mustache.render(template, { ...i18n[lang], - unsubscribe: `${backend}newsletter/unsubscribe/${sub.ehash}`, + unsubscribe: `https://freesewing.org${ + lang === 'en' ? '/' : '/' + lang + '/' + }newsletter/unsubscribe?x=${sub.ehash}`, content, }) console.log(`[${lang}] ${l}/${subs} (${i}) Sending to ${sub.email}`) diff --git a/sites/backend/src/controllers/subscribers.mjs b/sites/backend/src/controllers/subscribers.mjs index 2f44873a175..827783180f1 100644 --- a/sites/backend/src/controllers/subscribers.mjs +++ b/sites/backend/src/controllers/subscribers.mjs @@ -25,12 +25,12 @@ SubscribersController.prototype.subscribeConfirm = async (req, res, tools) => { } /* - * Unsubscribe confirmation + * Unsubscribe * See: https://freesewing.dev/reference/backend/api */ -SubscribersController.prototype.unsubscribeConfirm = async (req, res, tools) => { +SubscribersController.prototype.unsubscribe = async (req, res, tools) => { const Subscriber = new SubscriberModel(tools) - await Subscriber.unsubscribeConfirm(req) + await Subscriber.unsubscribe(req) return Subscriber.sendResponse(res) } diff --git a/sites/backend/src/models/subscriber.mjs b/sites/backend/src/models/subscriber.mjs index ffd05161972..458335508d6 100644 --- a/sites/backend/src/models/subscriber.mjs +++ b/sites/backend/src/models/subscriber.mjs @@ -9,6 +9,7 @@ export function SubscriberModel(tools) { return decorateModel(this, tools, { name: 'subscriber', encryptedFields: ['email'], + models: ['user'], }) } @@ -128,32 +129,48 @@ SubscriberModel.prototype.subscribeConfirm = async function ({ body }) { } /* - * Confirms a pending unsubscription - * This is an unauthenticated route + * Unsubscribe a user + * This is an unauthenticated route (has to for newsletter subscribers might not be users) * * @param {body} object - The request body * @returns {SubscriberModal} object - The SubscriberModel */ -SubscriberModel.prototype.unsubscribeConfirm = async function ({ params }) { +SubscriberModel.prototype.unsubscribe = async function ({ params }) { /* - * Validate input and load subscription record + * Is ehash set? */ - await this.verifySubscription(params) + if (!params.ehash) return this.setResponse(400, 'ehashMissing') + + const { ehash } = params /* - * If a status code is already set, do not continue + * Find the subscription record */ - if (this.response?.status) return this + await this.read({ ehash }) /* - * Remove the record + * If found, remove the record */ - await this.delete({ id: this.record.id }) + if (this.record) { + await this.delete({ id: this.record.id }) + + return this.setResponse(204) + } else { + /* + * If not, perhaps it's an account ehash rather than subscriber ehash + */ + await this.User.read({ ehash }) + if (this.User.record) { + await this.User.update({ newsletter: false }) + + return this.setResponse(204) + } + } /* - * Return 204 + * Return 404 */ - return this.setResponse(204) + return this.setResponse(404) } /* diff --git a/sites/backend/src/models/user.mjs b/sites/backend/src/models/user.mjs index add91fee088..84e602e8507 100644 --- a/sites/backend/src/models/user.mjs +++ b/sites/backend/src/models/user.mjs @@ -1675,6 +1675,7 @@ UserModel.prototype.asAccount = function () { consent: this.record.consent, control: this.record.control, createdAt: this.record.createdAt, + ehash: this.record.ehash, email: this.clear.email, data, ihash: this.record.ihash, diff --git a/sites/backend/src/routes/subscribers.mjs b/sites/backend/src/routes/subscribers.mjs index c572fd3f3f6..4381eb474f8 100644 --- a/sites/backend/src/routes/subscribers.mjs +++ b/sites/backend/src/routes/subscribers.mjs @@ -16,5 +16,5 @@ export function subscribersRoutes(tools) { app.put('/subscriber', (req, res) => Subscriber.subscribeConfirm(req, res, tools)) // Unsubscribe from newsletter - app.delete('/subscriber/:id/:ehash', (req, res) => Subscriber.unsubscribeConfirm(req, res, tools)) + app.delete('/subscriber/:ehash', (req, res) => Subscriber.unsubscribe(req, res, tools)) } diff --git a/sites/org/pages/newsletter/subscribe.mjs b/sites/org/pages/newsletter/subscribe.mjs index db314bcf847..e4d77039c75 100644 --- a/sites/org/pages/newsletter/subscribe.mjs +++ b/sites/org/pages/newsletter/subscribe.mjs @@ -33,7 +33,7 @@ const NewsletterPage = ({ page }) => { useEffect(() => { const newId = getSearchParam('id') - const newEhash = getSearchParam('ehash') + const newEhash = getSearchParam('check') if (newId !== id) setId(newId) if (newEhash !== ehash) setEhash(newEhash) }, [id, ehash]) @@ -49,6 +49,7 @@ const NewsletterPage = ({ page }) => { return ( +
{JSON.stringify({ id, ehash })}
) @@ -63,7 +64,7 @@ const NewsletterPage = ({ page }) => {

{t('newsletter:subscribePs')}

@@ -85,7 +86,7 @@ const NewsletterPage = ({ page }) => {

{t('newsletter:faqLead')}:{' '}

diff --git a/sites/org/pages/newsletter/unsubscribe.mjs b/sites/org/pages/newsletter/unsubscribe.mjs index 1d8567fd03b..e732acb3928 100644 --- a/sites/org/pages/newsletter/unsubscribe.mjs +++ b/sites/org/pages/newsletter/unsubscribe.mjs @@ -27,25 +27,22 @@ const NewsletterPage = ({ page }) => { const { setLoadingStatus } = useContext(LoadingStatusContext) const backend = useBackend() - const [confirmed, setConfirmed] = useState(false) - const [id, setId] = useState() const [ehash, setEhash] = useState() + const [done, setDone] = useState(false) useEffect(() => { - const newId = getSearchParam('id') - const newEhash = getSearchParam('ehash') - if (newId !== id) setId(newId) + const newEhash = getSearchParam('x') if (newEhash !== ehash) setEhash(newEhash) - }, [id, ehash]) + }, [ehash]) const handler = async () => { setLoadingStatus([true, 'status:contactingBackend']) - await backend.confirmNewsletterUnsubscribe({ id, ehash }) + await backend.newsletterUnsubscribe(ehash) setLoadingStatus([true, 'status:settingsSaved', true, true]) - setConfirmed(true) + setDone(true) } - if (!id || !ehash) + if (!ehash) return ( @@ -55,7 +52,7 @@ const NewsletterPage = ({ page }) => { return (
- {confirmed ? ( + {done ? ( <>

{t('newsletter:newsletter')}

{t('newsletter:thanksDone')}

@@ -76,7 +73,7 @@ const NewsletterPage = ({ page }) => {

{t('newsletter:faqLead')}:{' '}

diff --git a/sites/shared/components/account/newsletter.mjs b/sites/shared/components/account/newsletter.mjs index 12d1afeb7f5..528f6994d10 100644 --- a/sites/shared/components/account/newsletter.mjs +++ b/sites/shared/components/account/newsletter.mjs @@ -11,8 +11,10 @@ import { ContinueButton } from 'shared/components/buttons/continue-button.mjs' import { ListInput } from 'shared/components/inputs.mjs' import { DynamicMdx } from 'shared/components/mdx/dynamic.mjs' import { OkIcon, NoIcon } from 'shared/components/icons.mjs' +import { Popout } from 'shared/components/popout/index.mjs' +import { PageLink } from 'shared/components/link.mjs' -export const ns = ['account', 'status'] +export const ns = ['account', 'status', 'newsletter'] export const NewsletterSettings = ({ welcome = false, bare = false }) => { // Hooks @@ -89,6 +91,15 @@ export const NewsletterSettings = ({ welcome = false, bare = false }) => { ) : bare ? null : ( )} + +

{t('newsletter:subscribePs')}

+

+ +

+
) } diff --git a/sites/shared/components/wrappers/auth/index.mjs b/sites/shared/components/wrappers/auth/index.mjs index 2bed4118fa2..f53e0afeda2 100644 --- a/sites/shared/components/wrappers/auth/index.mjs +++ b/sites/shared/components/wrappers/auth/index.mjs @@ -127,7 +127,7 @@ const ConsentLacking = ({ banner, refresh }) => { export const AuthWrapper = ({ children, requiredRole = 'user' }) => { const { t } = useTranslation(ns) - const { account, token, admin, stopImpersonating, signOut } = useAccount() + const { account, setAccount, token, admin, stopImpersonating, signOut } = useAccount() const backend = useBackend() const [ready, setReady] = useState(false) @@ -151,7 +151,13 @@ export const AuthWrapper = ({ children, requiredRole = 'user' }) => { } const verifyUser = async () => { const result = await backend.ping() - if (!result.success) { + if (result.success) { + // Refresh account in local storage + setAccount({ + ...account, + ...result.data.account, + }) + } else { if (result.data?.error?.error) setError(result.data.error.error) else signOut() } diff --git a/sites/shared/hooks/use-backend.mjs b/sites/shared/hooks/use-backend.mjs index 6e6d0fb2cd0..3fe02b9d8ad 100644 --- a/sites/shared/hooks/use-backend.mjs +++ b/sites/shared/hooks/use-backend.mjs @@ -500,10 +500,10 @@ Backend.prototype.confirmNewsletterSubscribe = async function ({ id, ehash }) { } /* - * Confirm newsletter unsubscribe + * Newsletter unsubscribe */ -Backend.prototype.confirmNewsletterUnsubscribe = async function ({ id, ehash }) { - return responseHandler(await api.delete(`/subscriber/${id}/${ehash}`)) +Backend.prototype.newsletterUnsubscribe = async function (ehash) { + return responseHandler(await api.delete(`/subscriber/${ehash}`)) } /*