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.
This commit is contained in:
parent
f8feb5cf8b
commit
9208fb9a3c
10 changed files with 71 additions and 36 deletions
|
@ -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}`)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
<PageWrapper {...page} title={false}>
|
||||
<Hodl />
|
||||
<pre>{JSON.stringify({ id, ehash })}</pre>
|
||||
</PageWrapper>
|
||||
)
|
||||
|
||||
|
@ -63,7 +64,7 @@ const NewsletterPage = ({ page }) => {
|
|||
<p>{t('newsletter:subscribePs')}</p>
|
||||
<p>
|
||||
<PageLink
|
||||
href={`/newsletter/unsubscribe/${id}/${ehash}`}
|
||||
href={`/newsletter/unsubscribe?x=${ehash}`}
|
||||
txt={t('newsletter:unsubscribeLink')}
|
||||
/>
|
||||
</p>
|
||||
|
@ -85,7 +86,7 @@ const NewsletterPage = ({ page }) => {
|
|||
<p>
|
||||
{t('newsletter:faqLead')}:{' '}
|
||||
<PageLink
|
||||
href="/docs/faq/newsletter/why-subscribe-multiple-clicks"
|
||||
href="/docs/about/faq/newsletter/why-subscribe-multiple-clicks"
|
||||
txt={t('newsletter:subscribeWhy')}
|
||||
/>
|
||||
</p>
|
||||
|
|
|
@ -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 (
|
||||
<PageWrapper {...page} title={false}>
|
||||
<Hodl />
|
||||
|
@ -55,7 +52,7 @@ const NewsletterPage = ({ page }) => {
|
|||
return (
|
||||
<PageWrapper {...page} title={false}>
|
||||
<div className="max-w-xl">
|
||||
{confirmed ? (
|
||||
{done ? (
|
||||
<>
|
||||
<h1>{t('newsletter:newsletter')}</h1>
|
||||
<p>{t('newsletter:thanksDone')}</p>
|
||||
|
@ -76,7 +73,7 @@ const NewsletterPage = ({ page }) => {
|
|||
<p>
|
||||
{t('newsletter:faqLead')}:{' '}
|
||||
<PageLink
|
||||
href="/docs/faq/newsletter/why-unsubscribe-multiple-clicks"
|
||||
href="/docs/about/faq/newsletter/why-unsubscribe-multiple-clicks"
|
||||
txt={t('newsletter:unsubscribeWhy')}
|
||||
/>
|
||||
</p>
|
||||
|
|
|
@ -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 : (
|
||||
<BackToAccountButton />
|
||||
)}
|
||||
<Popout tip>
|
||||
<p>{t('newsletter:subscribePs')}</p>
|
||||
<p>
|
||||
<PageLink
|
||||
href={`/newsletter/unsubscribe?x=${account.ehash}`}
|
||||
txt={t('newsletter:unsubscribeLink')}
|
||||
/>
|
||||
</p>
|
||||
</Popout>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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}`))
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue