Skip to content

Commit f2f6ad9

Browse files
authored
feat(core): add toggle for newsletter in account settings (#2789)
* feat(core): add toggle for newsletter in account settings * fix: typo in action * chore: update success string
1 parent 82f5440 commit f2f6ad9

File tree

15 files changed

+603
-23
lines changed

15 files changed

+603
-23
lines changed

.changeset/hot-plums-give.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
"@bigcommerce/catalyst-core": patch
2+
"@bigcommerce/catalyst-core": minor
33
---
44

55
Make newsletter signup component on homepage render conditionally based on BigCommerce settings.

.changeset/tall-walls-tan.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
"@bigcommerce/catalyst-core": patch
2+
"@bigcommerce/catalyst-core": minor
33
---
44

55
Implement functional newsletter subscription feature with BigCommerce GraphQL API integration.
@@ -73,7 +73,7 @@ Add the following translation keys to your locale files (e.g., `messages/en.json
7373
"title": "Sign up for our newsletter",
7474
"placeholder": "Enter your email",
7575
"description": "Stay up to date with the latest news and offers from our store.",
76-
"subscribedToNewsletter": "You have been subscribed to our newsletter.",
76+
"subscribedToNewsletter": "You have been subscribed to our newsletter!",
7777
"Errors": {
7878
"invalidEmail": "Please enter a valid email address.",
7979
"somethingWentWrong": "Something went wrong. Please try again later."

.changeset/violet-adults-do.md

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
---
2+
"@bigcommerce/catalyst-core": minor
3+
---
4+
5+
Add newsletter subscription toggle to account settings page, allowing customers to manage their marketing preferences directly from their account.
6+
7+
## What Changed
8+
9+
- Added `NewsletterSubscriptionForm` component with a toggle switch for subscribing/unsubscribing to newsletters
10+
- Created `updateNewsletterSubscription` server action that handles both subscribe and unsubscribe operations via BigCommerce GraphQL API
11+
- Updated `AccountSettingsSection` to conditionally display the newsletter subscription form when enabled
12+
- Enhanced `CustomerSettingsQuery` to fetch `isSubscribedToNewsletter` status and `showNewsletterSignup` store setting
13+
- Updated account settings page to pass newsletter subscription props and bind customer info to the action
14+
- Added translation keys for newsletter subscription UI in `Account.Settings.NewsletterSubscription` namespace
15+
- Added E2E tests for subscribing and unsubscribing functionality
16+
17+
## Migration Guide
18+
19+
To add the newsletter subscription toggle to your account settings page:
20+
21+
### Step 1: Copy the server action
22+
23+
Copy the new server action file to your account settings directory:
24+
25+
```bash
26+
cp core/app/[locale]/(default)/account/settings/_actions/update-newsletter-subscription.ts \
27+
your-app/app/[locale]/(default)/account/settings/_actions/update-newsletter-subscription.ts
28+
```
29+
30+
### Step 2: Update the GraphQL query
31+
32+
Update `core/app/[locale]/(default)/account/settings/page-data.tsx` to include newsletter subscription fields:
33+
34+
```tsx
35+
// Renamed CustomerSettingsQuery to AccountSettingsQuery
36+
const AccountSettingsQuery = graphql(`
37+
query AccountSettingsQuery(...) {
38+
customer {
39+
...
40+
isSubscribedToNewsletter # Add this field
41+
}
42+
site {
43+
settings {
44+
...
45+
newsletter { # Add this section
46+
showNewsletterSignup
47+
}
48+
}
49+
}
50+
}
51+
`);
52+
```
53+
54+
Also update the return statement to include `newsletterSettings`:
55+
56+
```tsx
57+
const newsletterSettings = response.data.site.settings?.newsletter;
58+
59+
return {
60+
...
61+
newsletterSettings, // Add this
62+
};
63+
```
64+
65+
### Step 3: Copy the NewsletterSubscriptionForm component
66+
67+
Copy the new form component:
68+
69+
```bash
70+
cp core/vibes/soul/sections/account-settings/newsletter-subscription-form.tsx \
71+
your-app/vibes/soul/sections/account-settings/newsletter-subscription-form.tsx
72+
```
73+
74+
### Step 4: Update AccountSettingsSection
75+
76+
Update `core/vibes/soul/sections/account-settings/index.tsx`:
77+
78+
1. Import the new component:
79+
```tsx
80+
import {
81+
NewsletterSubscriptionForm,
82+
UpdateNewsletterSubscriptionAction,
83+
} from './newsletter-subscription-form';
84+
```
85+
86+
2. Add props to the interface:
87+
```tsx
88+
export interface AccountSettingsSectionProps {
89+
...
90+
newsletterSubscriptionEnabled?: boolean;
91+
isAccountSubscribed?: boolean;
92+
newsletterSubscriptionTitle?: string;
93+
newsletterSubscriptionLabel?: string;
94+
newsletterSubscriptionCtaLabel?: string;
95+
updateNewsletterSubscriptionAction?: UpdateNewsletterSubscriptionAction;
96+
}
97+
```
98+
99+
3. Add the form section in the component (after the change password form):
100+
```tsx
101+
{newsletterSubscriptionEnabled && updateNewsletterSubscriptionAction && (
102+
<div className="border-t border-[var(--account-settings-section-border,hsl(var(--contrast-100)))] pt-12">
103+
<h1 className="mb-10 font-[family-name:var(--account-settings-section-font-family,var(--font-family-heading))] text-2xl font-medium leading-none text-[var(--account-settings-section-text,var(--foreground))] @xl:text-2xl">
104+
{newsletterSubscriptionTitle}
105+
</h1>
106+
<NewsletterSubscriptionForm
107+
action={updateNewsletterSubscriptionAction}
108+
ctaLabel={newsletterSubscriptionCtaLabel}
109+
isAccountSubscribed={isAccountSubscribed}
110+
label={newsletterSubscriptionLabel}
111+
/>
112+
</div>
113+
)}
114+
```
115+
116+
### Step 5: Update the account settings page
117+
118+
Update `core/app/[locale]/(default)/account/settings/page.tsx`:
119+
120+
1. Import the action:
121+
```tsx
122+
import { updateNewsletterSubscription } from './_actions/update-newsletter-subscription';
123+
```
124+
125+
2. Extract newsletter settings from the query:
126+
```tsx
127+
const newsletterSubscriptionEnabled = accountSettings.storeSettings?.showNewsletterSignup;
128+
const isAccountSubscribed = accountSettings.customerInfo.isSubscribedToNewsletter;
129+
```
130+
131+
3. Bind customer info to the action:
132+
```tsx
133+
const updateNewsletterSubscriptionActionWithCustomerInfo = updateNewsletterSubscription.bind(
134+
null,
135+
{
136+
customerInfo: accountSettings.customerInfo,
137+
},
138+
);
139+
```
140+
141+
4. Pass props to `AccountSettingsSection`:
142+
```tsx
143+
<AccountSettingsSection
144+
...
145+
isAccountSubscribed={isAccountSubscribed}
146+
newsletterSubscriptionCtaLabel={t('cta')}
147+
newsletterSubscriptionEnabled={newsletterSubscriptionEnabled}
148+
newsletterSubscriptionLabel={t('NewsletterSubscription.label')}
149+
newsletterSubscriptionTitle={t('NewsletterSubscription.title')}
150+
updateNewsletterSubscriptionAction={updateNewsletterSubscriptionActionWithCustomerInfo}
151+
/>
152+
```
153+
154+
### Step 6: Add translation keys
155+
156+
Add the following keys to your locale files (e.g., `messages/en.json`):
157+
158+
```json
159+
{
160+
"Account": {
161+
"Settings": {
162+
...
163+
"NewsletterSubscription": {
164+
"title": "Marketing preferences",
165+
"label": "Opt-in to receive emails about new products and promotions.",
166+
"marketingPreferencesUpdated": "Marketing preferences have been updated successfully!",
167+
"somethingWentWrong": "Something went wrong. Please try again later."
168+
}
169+
}
170+
}
171+
}
172+
```
173+
174+
### Step 7: Verify the feature
175+
176+
1. Ensure your BigCommerce store has newsletter signup enabled in store settings
177+
2. Navigate to `/account/settings` as a logged-in customer
178+
3. Verify the newsletter subscription toggle appears below the change password form
179+
4. Test subscribing and unsubscribing functionality
180+
181+
The newsletter subscription form will only display if `newsletterSubscriptionEnabled` is `true` (controlled by the `showNewsletterSignup` store setting).
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
'use server';
2+
3+
import { BigCommerceGQLError } from '@bigcommerce/catalyst-client';
4+
import { SubmissionResult } from '@conform-to/react';
5+
import { parseWithZod } from '@conform-to/zod';
6+
import { unstable_expireTag } from 'next/cache';
7+
import { getTranslations } from 'next-intl/server';
8+
import { z } from 'zod';
9+
10+
import { client } from '~/client';
11+
import { graphql } from '~/client/graphql';
12+
import { TAGS } from '~/client/tags';
13+
14+
const updateNewsletterSubscriptionSchema = z.object({
15+
intent: z.enum(['subscribe', 'unsubscribe']),
16+
});
17+
18+
const SubscribeToNewsletterMutation = graphql(`
19+
mutation SubscribeToNewsletterMutation($input: CreateSubscriberInput!) {
20+
newsletter {
21+
subscribe(input: $input) {
22+
errors {
23+
__typename
24+
... on CreateSubscriberAlreadyExistsError {
25+
message
26+
}
27+
... on CreateSubscriberEmailInvalidError {
28+
message
29+
}
30+
... on CreateSubscriberUnexpectedError {
31+
message
32+
}
33+
... on CreateSubscriberLastNameInvalidError {
34+
message
35+
}
36+
... on CreateSubscriberFirstNameInvalidError {
37+
message
38+
}
39+
}
40+
}
41+
}
42+
}
43+
`);
44+
45+
const UnsubscribeFromNewsletterMutation = graphql(`
46+
mutation UnsubscribeFromNewsletterMutation($input: RemoveSubscriberInput!) {
47+
newsletter {
48+
unsubscribe(input: $input) {
49+
errors {
50+
__typename
51+
... on RemoveSubscriberEmailInvalidError {
52+
message
53+
}
54+
... on RemoveSubscriberUnexpectedError {
55+
message
56+
}
57+
}
58+
}
59+
}
60+
}
61+
`);
62+
63+
export const updateNewsletterSubscription = async (
64+
{
65+
customerInfo,
66+
}: {
67+
customerInfo: {
68+
email: string;
69+
firstName: string;
70+
lastName: string;
71+
};
72+
},
73+
_prevState: { lastResult: SubmissionResult | null },
74+
formData: FormData,
75+
) => {
76+
const t = await getTranslations('Account.Settings.NewsletterSubscription');
77+
78+
const submission = parseWithZod(formData, { schema: updateNewsletterSubscriptionSchema });
79+
80+
if (submission.status !== 'success') {
81+
return { lastResult: submission.reply() };
82+
}
83+
84+
try {
85+
let errors;
86+
87+
if (submission.value.intent === 'subscribe') {
88+
const response = await client.fetch({
89+
document: SubscribeToNewsletterMutation,
90+
variables: {
91+
input: {
92+
email: customerInfo.email,
93+
firstName: customerInfo.firstName,
94+
lastName: customerInfo.lastName,
95+
},
96+
},
97+
});
98+
99+
errors = response.data.newsletter.subscribe.errors;
100+
} else {
101+
const response = await client.fetch({
102+
document: UnsubscribeFromNewsletterMutation,
103+
variables: {
104+
input: {
105+
email: customerInfo.email,
106+
},
107+
},
108+
});
109+
110+
errors = response.data.newsletter.unsubscribe.errors;
111+
}
112+
113+
if (errors.length > 0) {
114+
// Not handling returned errors from API since we will display a generic error message to the user
115+
// Still returning the errors to the client for debugging purposes
116+
return {
117+
lastResult: submission.reply({
118+
formErrors: errors.map(({ message }) => message),
119+
}),
120+
};
121+
}
122+
123+
unstable_expireTag(TAGS.customer);
124+
125+
return {
126+
lastResult: submission.reply(),
127+
successMessage: t('marketingPreferencesUpdated'),
128+
};
129+
} catch (error) {
130+
// eslint-disable-next-line no-console
131+
console.error(error);
132+
133+
if (error instanceof BigCommerceGQLError) {
134+
return {
135+
lastResult: submission.reply({
136+
formErrors: error.errors.map(({ message }) => message),
137+
}),
138+
};
139+
}
140+
141+
if (error instanceof Error) {
142+
return {
143+
lastResult: submission.reply({ formErrors: [error.message] }),
144+
};
145+
}
146+
147+
return {
148+
lastResult: submission.reply({ formErrors: [String(error)] }),
149+
};
150+
}
151+
};

0 commit comments

Comments
 (0)