Skip to content

feat(frontend): Member subscription self management - #679

Open
libby-correctiv wants to merge 13 commits into
mainfrom
feat/member-subscription-self-management-rebased
Open

feat(frontend): Member subscription self management#679
libby-correctiv wants to merge 13 commits into
mainfrom
feat/member-subscription-self-management-rebased

Conversation

@libby-correctiv

@libby-correctiv libby-correctiv commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Adds a "Subscriptions" tab to the account page where members can view and unsubscribe from individual newsletter groups. Also makes group updates safer against a narrow race condition where a group added on Mailchimp's side could get overwritten if the sync webhook is down or hasn't caught up yet.

Changes

Frontend

  • New tabbed layout on the account page (Contact Information / Security / Subscriptions)
  • New AccountNewsletterSubscriptions.vue component and locale strings
  • New GET/DELETE /contact/:id/newsletter-groups[/:groupId] endpoints + client methods
  • Added a small info message reading "Group names will be displayed to members in the subscription management tab of the Account page" to the Integrations tab in the Settings page

Backend

  • Replaced the boolean mergeGroups option with NewsletterGroupChange ('add' | 'remove' | 'replace') - add/remove now scope the Mailchimp interests payload to only the group(s) being changed, instead of reasserting the full list, to avoid overwriting a group Mailchimp already knows about that beabee's local DB hasn't caught up on yet
  • Updates without groups now omit interests from the Mailchimp payload entirely, instead of reaffirming the full list on unrelated updates
  • Add a retry limit to NewsletterService.upsertContact's invalid-group recovery - previously retried indefinitely if a group ID was permanently invalid (not just stale), silently hanging instead of surfacing an error

Testing

  • new mailchimp.test.ts covering the payload shape for add/remove/replace; newsletter.test.ts updated. yarn test passing.

Screenshots

Screenshot 2026-08-19 at 11 16 07 Screenshot 2026-08-19 at 11 16 18

Checklist before requesting a review

  • Done a self-review of my code
  • Run yarn check and addressed any problems
  • PR doesn't have merge conflicts

Checklist before merging

  • Translations for all new i18n strings

Test scenarios

  • Someone joins via the join form, doesn't subscribe to any groups
  • Someone joins via the join form, no groups show, just subscribe tickbox
  • Someone joins via the join form, does subscribe to groups
  • Someone with an existing account answers a callout, updates groups
  • Someone with no account answers a callout, sets groups
  • Someone added via Mailchimp UI, groups are synced
  • Someone added/removed from Mailchimp UI

@libby-correctiv libby-correctiv changed the title Feat/member subscription self management rebased feat(frontend): Member subscription self management Aug 24, 2026
@libby-correctiv
libby-correctiv requested a review from wpf500 August 24, 2026 09:20

@wpf500 wpf500 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a really great implementation that re-uses a lot of the established patterns across the codebase. However I have added comments below, mostly to go through together, as I'd like to understand more the reason behind some of the changes.

Comment thread apps/frontend/src/pages/profile/account.vue Outdated
Comment thread apps/frontend/src/type/newsletter-group.ts Outdated
Comment thread packages/core/src/services/NewsletterService.ts Outdated
Comment thread packages/core/src/services/CalloutsService.ts
Comment thread apps/backend/src/api/controllers/ContactController.ts Outdated
Comment thread packages/core/src/utils/newsletter.ts
libby-correctiv and others added 5 commits August 31, 2026 14:25
Reuse accountPage.contactInformation and add accountPage.security
and accountPage.subscriptions for the remaining two tabs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Extract BaseNewsletterGroupData from NewsletterGroupData and use it for
the contact newsletter group endpoint across core, the DTO, the API
client and the frontend, replacing the frontend-local NewsletterGroup.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The read and write halves of the contact newsletter group endpoints lived
in different services. Move the unsubscribe alongside the read and call
upsertContact directly - updateContactProfile only forwarded to it, as it
strips newsletterGroups out of the fields it writes itself.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Return the resulting status and groups from upsertContactWithRetry and do
the ContactProfile write in upsertContact, so the write has one caller
instead of also being reachable through the retry recursion.

Scope the invalid-group retry to 'replace' updates. Only that path builds
the Mailchimp interests payload from the cached group list, so refreshing
the cache cannot change what add/remove sends - the retry re-sent an
identical payload and still failed, after a full group refresh.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Move contactToNlUpdate into upsertContact so the payload is built once and
passed to the provider call, which no longer rebuilds it on a retry or
duplicates the opts type in its signature.

Invert the retry guard to skip partial group updates instead of testing for
'replace', so the default no longer has to be restated alongside the one in
buildInterests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@wpf500 wpf500 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looks good, I'll do some testing on dev before approving

@wpf500 wpf500 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants