diff --git a/.changeset/afraid-islands-hang.md b/.changeset/afraid-islands-hang.md deleted file mode 100644 index a93ded4f5c..0000000000 --- a/.changeset/afraid-islands-hang.md +++ /dev/null @@ -1,132 +0,0 @@ ---- -"@bigcommerce/catalyst-core": minor ---- - -Upgrade c15t to 1.8.2, migrate from custom mode to offline mode, refactor consent cookie handling to use c15t's compact format, add script location support for HEAD/BODY rendering, and add privacy policy link support to CookieBanner. - -## What Changed - -- Upgraded `@c15t/nextjs` to version `1.8.2` -- Changed consent manager mode from `custom` (with endpoint handlers) to `offline` mode - - Removed custom `handlers.ts` implementation -- Added `enabled` prop to `C15TConsentManagerProvider` to control consent manager functionality -- Removed custom consent cookie encoder/decoder implementations (`decoder.ts`, `encoder.ts`) -- Added `parse-compact-format.ts` to handle c15t's compact cookie format - - Compact format: `i.t:timestamp,c.necessary:1,c.functionality:1,etc...` -- Updated cookie parsing logic in both client and server to use the new compact format parser -- Scripts now support `location` field from BigCommerce API and can be rendered in `` or `` based on the `target` property -- `CookieBanner` now supports the `privacyPolicyUrl` field from BigCommerce API and will be rendered in the banner description if available. - -## Migration Path - -### Consent Manager Provider Changes - -The `ConsentManagerProvider` now uses `offline` mode instead of `custom` mode with endpoint handlers. The provider configuration has been simplified: - -**Before:** -```typescript - showConsentBanner(isCookieConsentEnabled), - setConsent, - verifyConsent, - }, - }} -> - - {children} - - -``` - -**After:** -```typescript - - - {children} - - -``` - -**Key changes:** -- `mode` changed from `'custom'` to `'offline'` -- Removed `endpointHandlers` - no longer needed in offline mode -- Added `enabled` prop to control consent manager functionality -- Added `storageConfig` for cookie storage configuration - -### Cookie Handling - -If you have custom code that directly reads or writes consent cookies, you'll need to update it: - -**Before:** -The previous implementation used custom encoding/decoding. If you were directly accessing consent cookie values, you would have needed to use the custom decoder. - -**After:** -The consent cookie now uses c15t's compact format. The public API for reading cookies remains the same: - -```typescript -import { getConsentCookie } from '~/lib/consent-manager/cookies/client'; // client-side -// or -import { getConsentCookie } from '~/lib/consent-manager/cookies/server'; // server-side - -const consent = getConsentCookie(); -``` - -The `getConsentCookie()` function now internally uses `parseCompactFormat()` to parse the compact format cookie string. If you were directly parsing cookie values, you should now use the `getConsentCookie()` helper instead. - -`getConsentCookie` now returns a compact version of the consent values: - -```typescript -{ - i.t: 123456789, - c.necessary: true, - c.functionality: true, - c.marketing: false, - c.measurment: false -} -``` - -Updated instances where `getConsentCookie` is used to reflect this new schema. - -Removed `setConsentCookie` from server and client since this is now handled by the c15t library. - -### Script Location Support - -Scripts now support rendering in either `` or `` based on the `location` field from the BigCommerce API: - -```typescript -// Scripts transformer now includes target based on location -target: script.location === 'HEAD' ? 'head' : 'body' -``` - -The `ScriptsFragment` GraphQL query now includes the `location` field, allowing scripts to be placed in the appropriate DOM location. `FOOTER` location is still not supported. - -### Privacy Policy - -The `RootLayoutMetadataQuery` GraphQL query now includes the `privacyPolicyUrl` field, which renders a provicy policy link in the `CookieBanner` description. - -```typescript - -``` - -The privacy policy link: -- Opens in a new tab (`target="_blank"`) -- Only renders if `privacyPolicyUrl` is provided as a non-empty string - -Add translatable `privacyPolicy` field to `Components.ConsentManager.CookieBanner` translation namespace for the privacy policy link text. \ No newline at end of file diff --git a/.changeset/clean-days-search.md b/.changeset/clean-days-search.md deleted file mode 100644 index 11f21025b5..0000000000 --- a/.changeset/clean-days-search.md +++ /dev/null @@ -1,123 +0,0 @@ ---- -"@bigcommerce/catalyst-core": patch ---- - -Update /login/token route error handling and messaging - -## Migration steps - -### 1. Add `invalidToken` translation key to the `Auth.Login` namespace: -```json -"invalidToken": "Your login link is invalid or has expired. Please try logging in again.", -``` - -### 2. In `core/app/[locale]/(default)/(auth)/login/token/[token]/route.ts`, add a `console.error` in the `catch` block to log the error details: -```typescript -} catch (error) { - // eslint-disable-next-line no-console - console.error(error); - - // ... -} -``` - -### 3. In `core/app/[locale]/(default)/(auth)/login/page.tsx`, add `error` prop to searchParams and pass it down into the `SignInSection` component: -```typescript -export default async function Login({ params, searchParams }: Props) { - const { locale } = await params; - const { redirectTo = '/account/orders', error } = await searchParams; - - setRequestLocale(locale); - - const t = await getTranslations('Auth.Login'); - const vanityUrl = buildConfig.get('urls').vanityUrl; - const redirectUrl = new URL(redirectTo, vanityUrl); - const redirectTarget = redirectUrl.pathname + redirectUrl.search; - const tokenErrorMessage = error === 'InvalidToken' ? t('invalidToken') : undefined; - - return ( - <> - - { - // If the form errors change when an "error" search param is in the URL, - // the search param should be removed to prevent showing stale errors. - if (form.errors) { - const url = new URL(window.location.href); - - if (url.searchParams.has('error')) { - url.searchParams.delete('error'); - window.history.replaceState({}, '', url.toString()); - } - } - }, [form.errors]); - - const formErrors = () => { - // Form errors should take precedence over the error prop that is passed in. - // This ensures that the most recent errors are displayed to avoid confusion. - if (form.errors) { - return form.errors; - } - - if (error) { - return [error]; - } - - return []; - }; - - return ( -
- // ... - {submitLabel} - {formErrors().map((err, index) => ( - - {err} - - ))} -
- ); -} -``` - -### 6. Copy all changes in the `core/tests` directory diff --git a/.changeset/common-queens-smoke.md b/.changeset/common-queens-smoke.md deleted file mode 100644 index 2c704fbb3c..0000000000 --- a/.changeset/common-queens-smoke.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@bigcommerce/catalyst-core": patch ---- - -Passes `formButtonLabel` from `Reviews` to `ReviewsEmptyState` (was missing) and sets a default value for `formButtonLabel` diff --git a/.changeset/cute-trees-swim.md b/.changeset/cute-trees-swim.md deleted file mode 100644 index 243448e562..0000000000 --- a/.changeset/cute-trees-swim.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@bigcommerce/catalyst-core": minor ---- - -Conditionally display product ratings in the storefront based on `site.settings.display.showProductRating`. The storefront logic when this setting is enabled/disabled matches exactly the logic of Stencil + Cornerstone. diff --git a/.changeset/eleven-bugs-deny.md b/.changeset/eleven-bugs-deny.md deleted file mode 100644 index 4e5b3f96ef..0000000000 --- a/.changeset/eleven-bugs-deny.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@bigcommerce/catalyst-core": minor ---- - -Adds product review submission functionality to the product detail page via a modal form with validation for rating, title, review text, name, and email fields. Integrates with BigCommerce's GraphQL API using Conform and Zod for form validation and real-time feedback. diff --git a/.changeset/facets-displayName.md b/.changeset/facets-displayName.md deleted file mode 100644 index 65ab732250..0000000000 --- a/.changeset/facets-displayName.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@bigcommerce/catalyst-core": minor ---- - -Introduce displayName and displayKey fields to facets for improved labeling and filtering - -Facet filters now use the `displayName` field for more descriptive labels in the UI, replacing the deprecated `name` field. Product attribute facets now support the `filterKey` field for consistent parameter naming. The facet transformer has been updated to use `displayName` with a fallback to `filterName` when `displayName` is not available. diff --git a/.changeset/five-corners-try.md b/.changeset/five-corners-try.md deleted file mode 100644 index c4824c1f73..0000000000 --- a/.changeset/five-corners-try.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -"@bigcommerce/catalyst-core": patch ---- - -Remove "Exclusive Offers" field temporarily. Currently, the field is not fully implemented in GraphQL, so it may be misleading to display it on the storefront if it's not actually doing anything when registering a customer. - -Once the Register Customer operation takes this field into account, we can display it again. - -## Migration - -Update `core/app/[locale]/(default)/(auth)/register/page.tsx` and add the function: -```ts -// There is currently a GraphQL gap where the "Exclusive Offers" field isn't accounted for -// during customer registration, so the field should not be shown on the Catalyst storefront until it is hooked up. -function removeExlusiveOffersField(field: Field | Field[]): boolean { - if (Array.isArray(field)) { - // Exclusive offers field will always have ID '25', since it is made upon store creation and is also read-only. - return !field.some((f) => f.id === '25'); - } - - return field.id !== '25'; -} -``` - -Then, add the following code at the end of the `const fields` declaration: -```ts - }) - .filter(exists) - .filter(removeExlusiveOffersField); // <--- -``` diff --git a/.changeset/funny-dingos-accept.md b/.changeset/funny-dingos-accept.md deleted file mode 100644 index 700b7956ce..0000000000 --- a/.changeset/funny-dingos-accept.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@bigcommerce/catalyst-core': minor ---- - -Updated product and brand pages to include the number of reviews in the product data. Fixed visual spacing within product cards. Enhanced the Rating component to display the number of reviews alongside the rating. Introduced a new RatingLink component for smooth scrolling to reviews section on PDP. diff --git a/.changeset/hot-plums-give.md b/.changeset/hot-plums-give.md deleted file mode 100644 index e55241f7c8..0000000000 --- a/.changeset/hot-plums-give.md +++ /dev/null @@ -1,74 +0,0 @@ ---- -"@bigcommerce/catalyst-core": minor ---- - -Make newsletter signup component on homepage render conditionally based on BigCommerce settings. - -## What Changed - -- Newsletter signup component (`Subscribe`) on homepage now conditionally renders based on `showNewsletterSignup` setting from BigCommerce. -- Added `showNewsletterSignup` field to `HomePageQuery` GraphQL query to fetch newsletter settings. -- Newsletter signup now uses `Stream` component with `Streamable` pattern for progressive loading. - -## Migration - -To make newsletter signup component render conditionally based on BigCommerce settings, update your homepage code: - -### 1. Update GraphQL Query (`page-data.ts`) - -Add the `newsletter` field to your `HomePageQuery`: - -```typescript -const HomePageQuery = graphql( - ` - query HomePageQuery($currencyCode: currencyCode) { - site { - // ... existing fields - settings { - inventory { - defaultOutOfStockMessage - showOutOfStockMessage - showBackorderMessage - } - newsletter { - showNewsletterSignup - } - } - } - } - `, - [FeaturedProductsCarouselFragment, FeaturedProductsListFragment], -); -``` - -### 2. Update Homepage Component (`page.tsx`) - -Import `Stream` and create a streamable for newsletter settings: - -```typescript -import { Stream, Streamable } from '@/vibes/soul/lib/streamable'; - -// Inside your component, create the streamable: -const streamableShowNewsletterSignup = Streamable.from(async () => { - const data = await streamablePageData; - const { showNewsletterSignup } = data.site.settings?.newsletter ?? {}; - return showNewsletterSignup; -}); - -// Replace direct rendering with conditional Stream: - - {(showNewsletterSignup) => showNewsletterSignup && } - -``` - -**Before:** -```typescript - -``` - -**After:** -```typescript - - {(showNewsletterSignup) => showNewsletterSignup && } - -``` diff --git a/.changeset/metal-chicken-share.md b/.changeset/metal-chicken-share.md deleted file mode 100644 index 4812678b13..0000000000 --- a/.changeset/metal-chicken-share.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@bigcommerce/catalyst-core": minor ---- - -Refactor the `ReviewForm` to accept `trigger` prop instead of `formButtonLabel` for flexible rendering. diff --git a/.changeset/pink-zoos-attend.md b/.changeset/pink-zoos-attend.md deleted file mode 100644 index 8cc6def24f..0000000000 --- a/.changeset/pink-zoos-attend.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -"@bigcommerce/catalyst-core": patch ---- - -Add missing check for optional text field in `core/vibes/soul/form/dynamic-form/schema.ts`. - -## Migration - -Add `if (field.required !== true) fieldSchema = fieldSchema.optional();` to `text` case in `core/vibes/soul/form/dynamic-form/schema.ts`: - -```typescript -case 'text': - fieldSchema = z.string(); - - if (field.pattern != null) { - fieldSchema = fieldSchema.regex(new RegExp(field.pattern), { - message: 'Invalid format.', - }); - } - - if (field.required !== true) fieldSchema = fieldSchema.optional(); - - break; -``` diff --git a/.changeset/red-crews-move.md b/.changeset/red-crews-move.md deleted file mode 100644 index 5081c4a556..0000000000 --- a/.changeset/red-crews-move.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -"@bigcommerce/catalyst-core": patch ---- - -Improved login error handling to display a custom error message when BigCommerce indicates a password reset is required, instead of showing a generic error message. - -## What's Fixed - -When attempting to log in with an account that requires a password reset, users now see an informative error message: "Password reset required. Please check your email for instructions to reset your password." - -**Before**: Generic "something went wrong" error message -**After**: Clear error message explaining the password reset requirement - -## Migration - -### Step 1: Update Translation Files - -Add this translation key to your locale files (e.g., `core/messages/en.json`): - -```json -{ - "Auth": { - "Login": { - "passwordResetRequired": "Password reset required. Please check your email for instructions to reset your password.", - } - } -} -``` - -Repeat for all supported locales if you maintain custom translations. - -### Step 2: Update Login Server Action - -In your login server action (e.g., `core/app/[locale]/(default)/(auth)/login/_actions/login.ts`): - -Add the password reset error handling block: -```typescript -if ( - error instanceof AuthError && - error.type === 'CallbackRouteError' && - error.cause && - error.cause.err instanceof BigCommerceGQLError && - error.cause.err.message.includes('Reset password"') -) { - return submission.reply({ formErrors: [t('passwordResetRequired')] }); -} -``` - -This should be placed in your error handling, before the generic "Invalid credentials" check. diff --git a/.changeset/silent-pillows-sip.md b/.changeset/silent-pillows-sip.md deleted file mode 100644 index 9845142b09..0000000000 --- a/.changeset/silent-pillows-sip.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -"@bigcommerce/catalyst-core": minor ---- - -Adds OpenTelemetry instrumentation for Catalyst, enabling the collection of spans for Catalyst storefronts. - -### Migration - -Change is new code only, so just copy over `/core/instrumentation.ts` and `core/lib/otel/tracers.ts`. \ No newline at end of file diff --git a/.changeset/tall-walls-tan.md b/.changeset/tall-walls-tan.md deleted file mode 100644 index 8236fdf4df..0000000000 --- a/.changeset/tall-walls-tan.md +++ /dev/null @@ -1,84 +0,0 @@ ---- -"@bigcommerce/catalyst-core": minor ---- - -Implement functional newsletter subscription feature with BigCommerce GraphQL API integration. - -## What Changed - -- Replaced the mock implementation in `subscribe.ts` with a real BigCommerce GraphQL API call using the `SubscribeToNewsletterMutation`. -- Added comprehensive error handling for invalid emails, already-subscribed users, and unexpected errors. -- Improved form error handling in `InlineEmailForm` to use `form.errors` instead of field-level errors for better error display. -- Added comprehensive E2E tests and test fixtures for subscription functionality. - -## Migration Guide - -Replace the `subscribe` action in `core/components/subscribe/_actions/subscribe.ts` with the latest changes to include: -- BigCommerce GraphQL mutation for newsletter subscription -- Error handling for invalid emails, already-subscribed users, and unexpected errors -- Proper error messages returned via Conform's `submission.reply()` - -Update `inline-email-form` to fix issue of not showing server-side error messages from form actions. - -**`core/vibes/soul/primitives/inline-email-form/index.tsx`** - -1. Add import for `FieldError` component: -```tsx -import { FieldError } from '@/vibes/soul/form/field-error'; -``` - -2. Remove the field errors extraction: -```tsx -// Remove: const { errors = [] } = fields.email; -``` - -3. Update border styling to check both form and field errors: -```tsx -// Changed from: -errors.length ? 'border-error' : 'border-black', - -// Changed to: -form.errors?.length || fields.email.errors?.length - ? 'border-error focus-within:border-error' - : 'border-black focus-within:border-primary', -``` - -4. Update error rendering to display both field-level and form-level errors: -```tsx -// Changed from: -{errors.map((error, index) => ( - - {error} - -))} - -// Changed to: -{fields.email.errors?.map((error) => ( - {error} -))} -{form.errors?.map((error, index) => ( - - {error} - -))} -``` - -This change ensures that server-side error messages returned from form actions (like `formErrors` from Conform's `submission.reply()`) are now properly displayed to users. - -Add the following translation keys to your locale files (e.g., `messages/en.json`): -```json -{ - "Components": { - "Subscribe": { - "title": "Sign up for our newsletter", - "placeholder": "Enter your email", - "description": "Stay up to date with the latest news and offers from our store.", - "subscribedToNewsletter": "You have been subscribed to our newsletter!", - "Errors": { - "invalidEmail": "Please enter a valid email address.", - "somethingWentWrong": "Something went wrong. Please try again later." - } - } - } -} -``` diff --git a/.changeset/thin-things-repair.md b/.changeset/thin-things-repair.md deleted file mode 100644 index 844bd5546c..0000000000 --- a/.changeset/thin-things-repair.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@bigcommerce/catalyst-core": minor ---- - -Separate first and last name fields on user session object. diff --git a/.changeset/tiny-parts-call.md b/.changeset/tiny-parts-call.md deleted file mode 100644 index 3e86b2622f..0000000000 --- a/.changeset/tiny-parts-call.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@bigcommerce/catalyst-core": minor ---- - -Conditionally enable storefront reviews functionality based on `site.settings.reviews.enabled`. The storefront logic when this setting is enabled/disabled matches exactly the logic of Stencil + Cornerstone. diff --git a/.changeset/translations-patch-8b089a50.md b/.changeset/translations-patch-8b089a50.md deleted file mode 100644 index ad17b2636a..0000000000 --- a/.changeset/translations-patch-8b089a50.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@bigcommerce/catalyst-core": patch ---- - -Update translations. diff --git a/.changeset/translations-patch-c7dabfd2.md b/.changeset/translations-patch-c7dabfd2.md deleted file mode 100644 index ad17b2636a..0000000000 --- a/.changeset/translations-patch-c7dabfd2.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@bigcommerce/catalyst-core": patch ---- - -Update translations. diff --git a/.changeset/translations-patch-defea374.md b/.changeset/translations-patch-defea374.md deleted file mode 100644 index ad17b2636a..0000000000 --- a/.changeset/translations-patch-defea374.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@bigcommerce/catalyst-core": patch ---- - -Update translations. diff --git a/.changeset/translations-patch-e8270ecb.md b/.changeset/translations-patch-e8270ecb.md deleted file mode 100644 index ad17b2636a..0000000000 --- a/.changeset/translations-patch-e8270ecb.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@bigcommerce/catalyst-core": patch ---- - -Update translations. diff --git a/.changeset/tricky-plants-teach.md b/.changeset/tricky-plants-teach.md deleted file mode 100644 index 235066aa9d..0000000000 --- a/.changeset/tricky-plants-teach.md +++ /dev/null @@ -1,105 +0,0 @@ ---- -"@bigcommerce/catalyst-core": minor ---- - -Add out-of-stock / backorder message to product cards on PLPs based on store settings: -- Add out of stock message if the product is out of stock and stock is set to display it. -- Add the backorder message if the product has no on-hand stock and is available for backorder and the store/product is set to display the backorder message - -## Migration - -### Option 1: Automatic Migration (Recommended) -For existing Catalyst stores, the simplest way to get the newly added feature is to rebase the existing code with the new release code. The files that will be updated are listed below. - -### Option 2: Manual Migration -If you prefer not to rebase or have made customizations that prevent rebasing, follow these manual steps: - -#### Step 1: Update GraphQL Fragment -Add the inventory fields to your product card fragment in `core/components/product-card/fragment.ts` under `Product`: -```graphql -inventory { - hasVariantInventory - isInStock - aggregated { - availableForBackorder - unlimitedBackorder - availableOnHand - } -} -variants(first: 1) { - edges { - node { - entityId - sku - inventory { - byLocation { - edges { - node { - locationEntityId - backorderMessage - } - } - } - } - } - } -} -``` - -#### Step 2: Update Product interface in Product Card component -Update the `Product` interface in `core/vibes/soul/primitives/product-card/index.tsx` adding the following field to it: - -`inventoryMessage?: string;` - -#### Step 3: Update Data Transformer -Modify `core/data-transformers/product-card-transformer.ts` to include inventory message in the transformed data. You can simply copy the whole file from this release as it does not have UI breaking changes. - -#### Step 4: Update Product Card Layout -Update `core/vibes/soul/primitives/product-card/index.tsx` layout to display the new `inventoryMessage` product field. - -#### Step 5: Update Page Data GraphQL queries -Add inventory settings queries to the pages data. Add the following query to the main GQL query under `site.settings`: -``` -inventory { - defaultOutOfStockMessage - showOutOfStockMessage - showBackorderMessage -} -``` -to the following page data files: -- `core/app/[locale]/(default)/(faceted)/brand/[slug]/page-data.ts` -- `core/app/[locale]/(default)/(faceted)/category/[slug]/page-data.ts` -- `core/app/[locale]/(default)/(faceted)/search/page-data.ts` -- `core/app/[locale]/(default)/page-data.ts` - -#### Step 6: Update Page Components -Update the corresponding page components to use the `productCardTransformer` method (if not already using it) to get the product card, and pass inventory data to those product cards based on the store inventory settings. Use the following code while retrieving the product lists: -``` - const { defaultOutOfStockMessage, showOutOfStockMessage, showBackorderMessage } = - data.site.settings?.inventory ?? {}; - - return productCardTransformer( - featuredProducts, - format, - showOutOfStockMessage ? defaultOutOfStockMessage : undefined, - showBackorderMessage, - ); -``` -in the following files: -- `core/app/[locale]/(default)/(faceted)/brand/[slug]/page.tsx` -- `core/app/[locale]/(default)/(faceted)/category/[slug]/page.tsx` -- `core/app/[locale]/(default)/(faceted)/search/page.tsx` -- `core/app/[locale]/(default)/page.tsx` - -### Files Modified in This Change -- `core/app/[locale]/(default)/(faceted)/brand/[slug]/page-data.ts` -- `core/app/[locale]/(default)/(faceted)/brand/[slug]/page.tsx` -- `core/app/[locale]/(default)/(faceted)/category/[slug]/page-data.ts` -- `core/app/[locale]/(default)/(faceted)/category/[slug]/page.tsx` -- `core/app/[locale]/(default)/(faceted)/search/page-data.ts` -- `core/app/[locale]/(default)/(faceted)/search/page.tsx` -- `core/app/[locale]/(default)/page-data.ts` -- `core/app/[locale]/(default)/page.tsx` -- `core/components/product-card/fragment.ts` -- `core/data-transformers/product-card-transformer.ts` -- `core/vibes/soul/primitives/product-card/index.tsx` diff --git a/.changeset/violet-adults-do.md b/.changeset/violet-adults-do.md deleted file mode 100644 index 64f7b562ea..0000000000 --- a/.changeset/violet-adults-do.md +++ /dev/null @@ -1,181 +0,0 @@ ---- -"@bigcommerce/catalyst-core": minor ---- - -Add newsletter subscription toggle to account settings page, allowing customers to manage their marketing preferences directly from their account. - -## What Changed - -- Added `NewsletterSubscriptionForm` component with a toggle switch for subscribing/unsubscribing to newsletters -- Created `updateNewsletterSubscription` server action that handles both subscribe and unsubscribe operations via BigCommerce GraphQL API -- Updated `AccountSettingsSection` to conditionally display the newsletter subscription form when enabled -- Enhanced `CustomerSettingsQuery` to fetch `isSubscribedToNewsletter` status and `showNewsletterSignup` store setting -- Updated account settings page to pass newsletter subscription props and bind customer info to the action -- Added translation keys for newsletter subscription UI in `Account.Settings.NewsletterSubscription` namespace -- Added E2E tests for subscribing and unsubscribing functionality - -## Migration Guide - -To add the newsletter subscription toggle to your account settings page: - -### Step 1: Copy the server action - -Copy the new server action file to your account settings directory: - -```bash -cp core/app/[locale]/(default)/account/settings/_actions/update-newsletter-subscription.ts \ - your-app/app/[locale]/(default)/account/settings/_actions/update-newsletter-subscription.ts -``` - -### Step 2: Update the GraphQL query - -Update `core/app/[locale]/(default)/account/settings/page-data.tsx` to include newsletter subscription fields: - -```tsx -// Renamed CustomerSettingsQuery to AccountSettingsQuery -const AccountSettingsQuery = graphql(` - query AccountSettingsQuery(...) { - customer { - ... - isSubscribedToNewsletter # Add this field - } - site { - settings { - ... - newsletter { # Add this section - showNewsletterSignup - } - } - } - } -`); -``` - -Also update the return statement to include `newsletterSettings`: - -```tsx -const newsletterSettings = response.data.site.settings?.newsletter; - -return { - ... - newsletterSettings, // Add this -}; -``` - -### Step 3: Copy the NewsletterSubscriptionForm component - -Copy the new form component: - -```bash -cp core/vibes/soul/sections/account-settings/newsletter-subscription-form.tsx \ - your-app/vibes/soul/sections/account-settings/newsletter-subscription-form.tsx -``` - -### Step 4: Update AccountSettingsSection - -Update `core/vibes/soul/sections/account-settings/index.tsx`: - -1. Import the new component: -```tsx -import { - NewsletterSubscriptionForm, - UpdateNewsletterSubscriptionAction, -} from './newsletter-subscription-form'; -``` - -2. Add props to the interface: -```tsx -export interface AccountSettingsSectionProps { - ... - newsletterSubscriptionEnabled?: boolean; - isAccountSubscribed?: boolean; - newsletterSubscriptionTitle?: string; - newsletterSubscriptionLabel?: string; - newsletterSubscriptionCtaLabel?: string; - updateNewsletterSubscriptionAction?: UpdateNewsletterSubscriptionAction; -} -``` - -3. Add the form section in the component (after the change password form): -```tsx -{newsletterSubscriptionEnabled && updateNewsletterSubscriptionAction && ( -
-

- {newsletterSubscriptionTitle} -

- -
-)} -``` - -### Step 5: Update the account settings page - -Update `core/app/[locale]/(default)/account/settings/page.tsx`: - -1. Import the action: -```tsx -import { updateNewsletterSubscription } from './_actions/update-newsletter-subscription'; -``` - -2. Extract newsletter settings from the query: -```tsx -const newsletterSubscriptionEnabled = accountSettings.storeSettings?.showNewsletterSignup; -const isAccountSubscribed = accountSettings.customerInfo.isSubscribedToNewsletter; -``` - -3. Bind customer info to the action: -```tsx -const updateNewsletterSubscriptionActionWithCustomerInfo = updateNewsletterSubscription.bind( - null, - { - customerInfo: accountSettings.customerInfo, - }, -); -``` - -4. Pass props to `AccountSettingsSection`: -```tsx - -``` - -### Step 6: Add translation keys - -Add the following keys to your locale files (e.g., `messages/en.json`): - -```json -{ - "Account": { - "Settings": { - ... - "NewsletterSubscription": { - "title": "Marketing preferences", - "label": "Opt-in to receive emails about new products and promotions.", - "marketingPreferencesUpdated": "Marketing preferences have been updated successfully!", - "somethingWentWrong": "Something went wrong. Please try again later." - } - } - } -} -``` - -### Step 7: Verify the feature - -1. Ensure your BigCommerce store has newsletter signup enabled in store settings -2. Navigate to `/account/settings` as a logged-in customer -3. Verify the newsletter subscription toggle appears below the change password form -4. Test subscribing and unsubscribing functionality - -The newsletter subscription form will only display if `newsletterSubscriptionEnabled` is `true` (controlled by the `showNewsletterSignup` store setting). diff --git a/.changeset/wet-cameras-turn.md b/.changeset/wet-cameras-turn.md deleted file mode 100644 index 9bdddd340e..0000000000 --- a/.changeset/wet-cameras-turn.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -"@bigcommerce/catalyst-core": minor ---- - -Add the following backorder messages to PDP based on the store inventory settings and the product backorders data: -- Backorder availability prompt -- Quantity on backorder -- Backorder message - -## Migration -For existing Catalyst stores, to get the newly added feature, simply rebase the existing code with the new release code. The files to be rebased for this change to be applied are: -- core/messages/en.json -- core/app/[locale]/(default)/product/[slug]/page-data.ts -- core/app/[locale]/(default)/product/[slug]/page.tsx -- core/app/[locale]/(default)/product/[slug]/_components/product-viewed/fragment.ts -- core/vibes/soul/sections/product-detail/index.tsx -- core/vibes/soul/sections/product-detail/product-detail-form.tsx \ No newline at end of file diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index 6389b7d38a..4b65096551 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -1,5 +1,865 @@ # Changelog +## 1.4.0 + +### Minor Changes + +- [#2768](https://github.com/bigcommerce/catalyst/pull/2768) [`9782209`](https://github.com/bigcommerce/catalyst/commit/97822092283ff0f631d090b00533b515f8d2eceb) Thanks [@jorgemoya](https://github.com/jorgemoya)! - Upgrade c15t to 1.8.2, migrate from custom mode to offline mode, refactor consent cookie handling to use c15t's compact format, add script location support for HEAD/BODY rendering, and add privacy policy link support to CookieBanner. + + ## What Changed + - Upgraded `@c15t/nextjs` to version `1.8.2` + - Changed consent manager mode from `custom` (with endpoint handlers) to `offline` mode + - Removed custom `handlers.ts` implementation + - Added `enabled` prop to `C15TConsentManagerProvider` to control consent manager functionality + - Removed custom consent cookie encoder/decoder implementations (`decoder.ts`, `encoder.ts`) + - Added `parse-compact-format.ts` to handle c15t's compact cookie format + - Compact format: `i.t:timestamp,c.necessary:1,c.functionality:1,etc...` + - Updated cookie parsing logic in both client and server to use the new compact format parser + - Scripts now support `location` field from BigCommerce API and can be rendered in `` or `` based on the `target` property + - `CookieBanner` now supports the `privacyPolicyUrl` field from BigCommerce API and will be rendered in the banner description if available. + + ## Migration Path + + ### Consent Manager Provider Changes + + The `ConsentManagerProvider` now uses `offline` mode instead of `custom` mode with endpoint handlers. The provider configuration has been simplified: + + **Before:** + + ```typescript + showConsentBanner(isCookieConsentEnabled), + setConsent, + verifyConsent, + }, + }} + > + + {children} + + + ``` + + **After:** + + ```typescript + + + {children} + + + ``` + + **Key changes:** + - `mode` changed from `'custom'` to `'offline'` + - Removed `endpointHandlers` - no longer needed in offline mode + - Added `enabled` prop to control consent manager functionality + - Added `storageConfig` for cookie storage configuration + + ### Cookie Handling + + If you have custom code that directly reads or writes consent cookies, you'll need to update it: + + **Before:** + The previous implementation used custom encoding/decoding. If you were directly accessing consent cookie values, you would have needed to use the custom decoder. + + **After:** + The consent cookie now uses c15t's compact format. The public API for reading cookies remains the same: + + ```typescript + import { getConsentCookie } from '~/lib/consent-manager/cookies/client'; // client-side + // or + import { getConsentCookie } from '~/lib/consent-manager/cookies/server'; // server-side + + const consent = getConsentCookie(); + ``` + + The `getConsentCookie()` function now internally uses `parseCompactFormat()` to parse the compact format cookie string. If you were directly parsing cookie values, you should now use the `getConsentCookie()` helper instead. + + `getConsentCookie` now returns a compact version of the consent values: + + ```typescript + { + i.t: 123456789, + c.necessary: true, + c.functionality: true, + c.marketing: false, + c.measurment: false + } + ``` + + Updated instances where `getConsentCookie` is used to reflect this new schema. + + Removed `setConsentCookie` from server and client since this is now handled by the c15t library. + + ### Script Location Support + + Scripts now support rendering in either `` or `` based on the `location` field from the BigCommerce API: + + ```typescript + // Scripts transformer now includes target based on location + target: script.location === 'HEAD' ? 'head' : 'body'; + ``` + + The `ScriptsFragment` GraphQL query now includes the `location` field, allowing scripts to be placed in the appropriate DOM location. `FOOTER` location is still not supported. + + ### Privacy Policy + + The `RootLayoutMetadataQuery` GraphQL query now includes the `privacyPolicyUrl` field, which renders a provicy policy link in the `CookieBanner` description. + + ```typescript + + ``` + + The privacy policy link: + - Opens in a new tab (`target="_blank"`) + - Only renders if `privacyPolicyUrl` is provided as a non-empty string + + Add translatable `privacyPolicy` field to `Components.ConsentManager.CookieBanner` translation namespace for the privacy policy link text. + +- [#2754](https://github.com/bigcommerce/catalyst/pull/2754) [`4cbc124`](https://github.com/bigcommerce/catalyst/commit/4cbc124f3c63de58b97cba829aa9b63ecab6a157) Thanks [@matthewvolk](https://github.com/matthewvolk)! - Conditionally display product ratings in the storefront based on `site.settings.display.showProductRating`. The storefront logic when this setting is enabled/disabled matches exactly the logic of Stencil + Cornerstone. + +- [#2709](https://github.com/bigcommerce/catalyst/pull/2709) [`3820a75`](https://github.com/bigcommerce/catalyst/commit/3820a754def724ddd335e7b36d5ac2633b6e23e2) Thanks [@jordanarldt](https://github.com/jordanarldt)! - Adds product review submission functionality to the product detail page via a modal form with validation for rating, title, review text, name, and email fields. Integrates with BigCommerce's GraphQL API using Conform and Zod for form validation and real-time feedback. + +- [#2690](https://github.com/bigcommerce/catalyst/pull/2690) [`44f6bc0`](https://github.com/bigcommerce/catalyst/commit/44f6bc0e2549909fc2ff7dd456cc462fd0eafcde) Thanks [@jfugalde](https://github.com/jfugalde)! - Introduce displayName and displayKey fields to facets for improved labeling and filtering + + Facet filters now use the `displayName` field for more descriptive labels in the UI, replacing the deprecated `name` field. Product attribute facets now support the `filterKey` field for consistent parameter naming. The facet transformer has been updated to use `displayName` with a fallback to `filterName` when `displayName` is not available. + +- [#2756](https://github.com/bigcommerce/catalyst/pull/2756) [`0e867a7`](https://github.com/bigcommerce/catalyst/commit/0e867a76f888c54112717c4a1f45c2d64dbbac7c) Thanks [@matthewvolk](https://github.com/matthewvolk)! - Updated product and brand pages to include the number of reviews in the product data. Fixed visual spacing within product cards. Enhanced the Rating component to display the number of reviews alongside the rating. Introduced a new RatingLink component for smooth scrolling to reviews section on PDP. + +- [#2783](https://github.com/bigcommerce/catalyst/pull/2783) [`052c147`](https://github.com/bigcommerce/catalyst/commit/052c1476fc70d1187d88f4c95245d113903369a0) Thanks [@jorgemoya](https://github.com/jorgemoya)! - Make newsletter signup component on homepage render conditionally based on BigCommerce settings. + + ## What Changed + - Newsletter signup component (`Subscribe`) on homepage now conditionally renders based on `showNewsletterSignup` setting from BigCommerce. + - Added `showNewsletterSignup` field to `HomePageQuery` GraphQL query to fetch newsletter settings. + - Newsletter signup now uses `Stream` component with `Streamable` pattern for progressive loading. + + ## Migration + + To make newsletter signup component render conditionally based on BigCommerce settings, update your homepage code: + + ### 1. Update GraphQL Query (`page-data.ts`) + + Add the `newsletter` field to your `HomePageQuery`: + + ```typescript + const HomePageQuery = graphql( + ` + query HomePageQuery($currencyCode: currencyCode) { + site { + // ... existing fields + settings { + inventory { + defaultOutOfStockMessage + showOutOfStockMessage + showBackorderMessage + } + newsletter { + showNewsletterSignup + } + } + } + } + `, + [FeaturedProductsCarouselFragment, FeaturedProductsListFragment], + ); + ``` + + ### 2. Update Homepage Component (`page.tsx`) + + Import `Stream` and create a streamable for newsletter settings: + + ```typescript + import { Stream, Streamable } from '@/vibes/soul/lib/streamable'; + + // Inside your component, create the streamable: + const streamableShowNewsletterSignup = Streamable.from(async () => { + const data = await streamablePageData; + const { showNewsletterSignup } = data.site.settings?.newsletter ?? {}; + return showNewsletterSignup; + }); + + // Replace direct rendering with conditional Stream: + + {(showNewsletterSignup) => showNewsletterSignup && } + + ``` + + **Before:** + + ```typescript + + ``` + + **After:** + + ```typescript + + {(showNewsletterSignup) => showNewsletterSignup && } + + ``` + +- [#2753](https://github.com/bigcommerce/catalyst/pull/2753) [`7927d26`](https://github.com/bigcommerce/catalyst/commit/7927d2673b264e52253ee6aeaa287eafe5b0bc9d) Thanks [@matthewvolk](https://github.com/matthewvolk)! - Refactor the `ReviewForm` to accept `trigger` prop instead of `formButtonLabel` for flexible rendering. + +- [#2708](https://github.com/bigcommerce/catalyst/pull/2708) [`aa35bec`](https://github.com/bigcommerce/catalyst/commit/aa35bec2dd45c7a8280b2583e830deafe666277e) Thanks [@jordanarldt](https://github.com/jordanarldt)! - Adds OpenTelemetry instrumentation for Catalyst, enabling the collection of spans for Catalyst storefronts. + + ### Migration + + Change is new code only, so just copy over `/core/instrumentation.ts` and `core/lib/otel/tracers.ts`. + +- [#2784](https://github.com/bigcommerce/catalyst/pull/2784) [`7c626a7`](https://github.com/bigcommerce/catalyst/commit/7c626a74be82a27b95710baaefe4977f573b6488) Thanks [@jorgemoya](https://github.com/jorgemoya)! - Implement functional newsletter subscription feature with BigCommerce GraphQL API integration. + + ## What Changed + - Replaced the mock implementation in `subscribe.ts` with a real BigCommerce GraphQL API call using the `SubscribeToNewsletterMutation`. + - Added comprehensive error handling for invalid emails, already-subscribed users, and unexpected errors. + - Improved form error handling in `InlineEmailForm` to use `form.errors` instead of field-level errors for better error display. + - Added comprehensive E2E tests and test fixtures for subscription functionality. + + ## Migration Guide + + Replace the `subscribe` action in `core/components/subscribe/_actions/subscribe.ts` with the latest changes to include: + - BigCommerce GraphQL mutation for newsletter subscription + - Error handling for invalid emails, already-subscribed users, and unexpected errors + - Proper error messages returned via Conform's `submission.reply()` + + Update `inline-email-form` to fix issue of not showing server-side error messages from form actions. + + **`core/vibes/soul/primitives/inline-email-form/index.tsx`** + 1. Add import for `FieldError` component: + + ```tsx + import { FieldError } from '@/vibes/soul/form/field-error'; + ``` + + 2. Remove the field errors extraction: + + ```tsx + // Remove: const { errors = [] } = fields.email; + ``` + + 3. Update border styling to check both form and field errors: + + ```tsx + // Changed from: + errors.length ? 'border-error' : 'border-black', + + // Changed to: + form.errors?.length || fields.email.errors?.length + ? 'border-error focus-within:border-error' + : 'border-black focus-within:border-primary', + ``` + + 4. Update error rendering to display both field-level and form-level errors: + + ```tsx + // Changed from: + { + errors.map((error, index) => ( + + {error} + + )); + } + + // Changed to: + { + fields.email.errors?.map((error) => {error}); + } + { + form.errors?.map((error, index) => ( + + {error} + + )); + } + ``` + + This change ensures that server-side error messages returned from form actions (like `formErrors` from Conform's `submission.reply()`) are now properly displayed to users. + + Add the following translation keys to your locale files (e.g., `messages/en.json`): + + ```json + { + "Components": { + "Subscribe": { + "title": "Sign up for our newsletter", + "placeholder": "Enter your email", + "description": "Stay up to date with the latest news and offers from our store.", + "subscribedToNewsletter": "You have been subscribed to our newsletter!", + "Errors": { + "invalidEmail": "Please enter a valid email address.", + "somethingWentWrong": "Something went wrong. Please try again later." + } + } + } + } + ``` + +- [#2711](https://github.com/bigcommerce/catalyst/pull/2711) [`fcd0836`](https://github.com/bigcommerce/catalyst/commit/fcd08369ed17e97619e98c2f71cf7c2fa8467906) Thanks [@jordanarldt](https://github.com/jordanarldt)! - Separate first and last name fields on user session object. + +- [#2752](https://github.com/bigcommerce/catalyst/pull/2752) [`4631b88`](https://github.com/bigcommerce/catalyst/commit/4631b88b49539cd30927d08554f96022ff014c8d) Thanks [@matthewvolk](https://github.com/matthewvolk)! - Conditionally enable storefront reviews functionality based on `site.settings.reviews.enabled`. The storefront logic when this setting is enabled/disabled matches exactly the logic of Stencil + Cornerstone. + +- [#2739](https://github.com/bigcommerce/catalyst/pull/2739) [`e155398`](https://github.com/bigcommerce/catalyst/commit/e15539873c92a4b2791d992675c20e7e50a64ad2) Thanks [@Tharaae](https://github.com/Tharaae)! - Add out-of-stock / backorder message to product cards on PLPs based on store settings: + - Add out of stock message if the product is out of stock and stock is set to display it. + - Add the backorder message if the product has no on-hand stock and is available for backorder and the store/product is set to display the backorder message + + ## Migration + + ### Option 1: Automatic Migration (Recommended) + + For existing Catalyst stores, the simplest way to get the newly added feature is to rebase the existing code with the new release code. The files that will be updated are listed below. + + ### Option 2: Manual Migration + + If you prefer not to rebase or have made customizations that prevent rebasing, follow these manual steps: + + #### Step 1: Update GraphQL Fragment + + Add the inventory fields to your product card fragment in `core/components/product-card/fragment.ts` under `Product`: + + ```graphql + inventory { + hasVariantInventory + isInStock + aggregated { + availableForBackorder + unlimitedBackorder + availableOnHand + } + } + variants(first: 1) { + edges { + node { + entityId + sku + inventory { + byLocation { + edges { + node { + locationEntityId + backorderMessage + } + } + } + } + } + } + } + ``` + + #### Step 2: Update Product interface in Product Card component + + Update the `Product` interface in `core/vibes/soul/primitives/product-card/index.tsx` adding the following field to it: + + `inventoryMessage?: string;` + + #### Step 3: Update Data Transformer + + Modify `core/data-transformers/product-card-transformer.ts` to include inventory message in the transformed data. You can simply copy the whole file from this release as it does not have UI breaking changes. + + #### Step 4: Update Product Card Layout + + Update `core/vibes/soul/primitives/product-card/index.tsx` layout to display the new `inventoryMessage` product field. + + #### Step 5: Update Page Data GraphQL queries + + Add inventory settings queries to the pages data. Add the following query to the main GQL query under `site.settings`: + + ``` + inventory { + defaultOutOfStockMessage + showOutOfStockMessage + showBackorderMessage + } + ``` + + to the following page data files: + - `core/app/[locale]/(default)/(faceted)/brand/[slug]/page-data.ts` + - `core/app/[locale]/(default)/(faceted)/category/[slug]/page-data.ts` + - `core/app/[locale]/(default)/(faceted)/search/page-data.ts` + - `core/app/[locale]/(default)/page-data.ts` + + #### Step 6: Update Page Components + + Update the corresponding page components to use the `productCardTransformer` method (if not already using it) to get the product card, and pass inventory data to those product cards based on the store inventory settings. Use the following code while retrieving the product lists: + + ``` + const { defaultOutOfStockMessage, showOutOfStockMessage, showBackorderMessage } = + data.site.settings?.inventory ?? {}; + + return productCardTransformer( + featuredProducts, + format, + showOutOfStockMessage ? defaultOutOfStockMessage : undefined, + showBackorderMessage, + ); + ``` + + in the following files: + - `core/app/[locale]/(default)/(faceted)/brand/[slug]/page.tsx` + - `core/app/[locale]/(default)/(faceted)/category/[slug]/page.tsx` + - `core/app/[locale]/(default)/(faceted)/search/page.tsx` + - `core/app/[locale]/(default)/page.tsx` + + ### Files Modified in This Change + - `core/app/[locale]/(default)/(faceted)/brand/[slug]/page-data.ts` + - `core/app/[locale]/(default)/(faceted)/brand/[slug]/page.tsx` + - `core/app/[locale]/(default)/(faceted)/category/[slug]/page-data.ts` + - `core/app/[locale]/(default)/(faceted)/category/[slug]/page.tsx` + - `core/app/[locale]/(default)/(faceted)/search/page-data.ts` + - `core/app/[locale]/(default)/(faceted)/search/page.tsx` + - `core/app/[locale]/(default)/page-data.ts` + - `core/app/[locale]/(default)/page.tsx` + - `core/components/product-card/fragment.ts` + - `core/data-transformers/product-card-transformer.ts` + - `core/vibes/soul/primitives/product-card/index.tsx` + +- [#2789](https://github.com/bigcommerce/catalyst/pull/2789) [`f2f6ad9`](https://github.com/bigcommerce/catalyst/commit/f2f6ad973291906022c046e372d8b8f8fcc9e292) Thanks [@jorgemoya](https://github.com/jorgemoya)! - Add newsletter subscription toggle to account settings page, allowing customers to manage their marketing preferences directly from their account. + + ## What Changed + - Added `NewsletterSubscriptionForm` component with a toggle switch for subscribing/unsubscribing to newsletters + - Created `updateNewsletterSubscription` server action that handles both subscribe and unsubscribe operations via BigCommerce GraphQL API + - Updated `AccountSettingsSection` to conditionally display the newsletter subscription form when enabled + - Enhanced `CustomerSettingsQuery` to fetch `isSubscribedToNewsletter` status and `showNewsletterSignup` store setting + - Updated account settings page to pass newsletter subscription props and bind customer info to the action + - Added translation keys for newsletter subscription UI in `Account.Settings.NewsletterSubscription` namespace + - Added E2E tests for subscribing and unsubscribing functionality + + ## Migration Guide + + To add the newsletter subscription toggle to your account settings page: + + ### Step 1: Copy the server action + + Copy the new server action file to your account settings directory: + + ```bash + cp core/app/[locale]/(default)/account/settings/_actions/update-newsletter-subscription.ts \ + your-app/app/[locale]/(default)/account/settings/_actions/update-newsletter-subscription.ts + ``` + + ### Step 2: Update the GraphQL query + + Update `core/app/[locale]/(default)/account/settings/page-data.tsx` to include newsletter subscription fields: + + ```tsx + // Renamed CustomerSettingsQuery to AccountSettingsQuery + const AccountSettingsQuery = graphql(` + query AccountSettingsQuery(...) { + customer { + ... + isSubscribedToNewsletter # Add this field + } + site { + settings { + ... + newsletter { # Add this section + showNewsletterSignup + } + } + } + } + `); + ``` + + Also update the return statement to include `newsletterSettings`: + + ```tsx + const newsletterSettings = response.data.site.settings?.newsletter; + + return { + ...newsletterSettings, // Add this + }; + ``` + + ### Step 3: Copy the NewsletterSubscriptionForm component + + Copy the new form component: + + ```bash + cp core/vibes/soul/sections/account-settings/newsletter-subscription-form.tsx \ + your-app/vibes/soul/sections/account-settings/newsletter-subscription-form.tsx + ``` + + ### Step 4: Update AccountSettingsSection + + Update `core/vibes/soul/sections/account-settings/index.tsx`: + 1. Import the new component: + + ```tsx + import { + NewsletterSubscriptionForm, + UpdateNewsletterSubscriptionAction, + } from './newsletter-subscription-form'; + ``` + + 2. Add props to the interface: + + ```tsx + export interface AccountSettingsSectionProps { + ... + newsletterSubscriptionEnabled?: boolean; + isAccountSubscribed?: boolean; + newsletterSubscriptionTitle?: string; + newsletterSubscriptionLabel?: string; + newsletterSubscriptionCtaLabel?: string; + updateNewsletterSubscriptionAction?: UpdateNewsletterSubscriptionAction; + } + ``` + + 3. Add the form section in the component (after the change password form): + + ```tsx + { + newsletterSubscriptionEnabled && updateNewsletterSubscriptionAction && ( +
+

+ {newsletterSubscriptionTitle} +

+ +
+ ); + } + ``` + + ### Step 5: Update the account settings page + + Update `core/app/[locale]/(default)/account/settings/page.tsx`: + 1. Import the action: + + ```tsx + import { updateNewsletterSubscription } from './_actions/update-newsletter-subscription'; + ``` + + 2. Extract newsletter settings from the query: + + ```tsx + const newsletterSubscriptionEnabled = accountSettings.storeSettings?.showNewsletterSignup; + const isAccountSubscribed = accountSettings.customerInfo.isSubscribedToNewsletter; + ``` + + 3. Bind customer info to the action: + + ```tsx + const updateNewsletterSubscriptionActionWithCustomerInfo = updateNewsletterSubscription.bind( + null, + { + customerInfo: accountSettings.customerInfo, + }, + ); + ``` + + 4. Pass props to `AccountSettingsSection`: + + ```tsx + + ``` + + ### Step 6: Add translation keys + + Add the following keys to your locale files (e.g., `messages/en.json`): + + ```json + { + "Account": { + "Settings": { + ... + "NewsletterSubscription": { + "title": "Marketing preferences", + "label": "Opt-in to receive emails about new products and promotions.", + "marketingPreferencesUpdated": "Marketing preferences have been updated successfully!", + "somethingWentWrong": "Something went wrong. Please try again later." + } + } + } + } + ``` + + ### Step 7: Verify the feature + 1. Ensure your BigCommerce store has newsletter signup enabled in store settings + 2. Navigate to `/account/settings` as a logged-in customer + 3. Verify the newsletter subscription toggle appears below the change password form + 4. Test subscribing and unsubscribing functionality + + The newsletter subscription form will only display if `newsletterSubscriptionEnabled` is `true` (controlled by the `showNewsletterSignup` store setting). + +- [#2733](https://github.com/bigcommerce/catalyst/pull/2733) [`9f70d2e`](https://github.com/bigcommerce/catalyst/commit/9f70d2e830c276a9742cf542cb03ee09a50a969e) Thanks [@Tharaae](https://github.com/Tharaae)! - Add the following backorder messages to PDP based on the store inventory settings and the product backorders data: + - Backorder availability prompt + - Quantity on backorder + - Backorder message + + ## Migration + + For existing Catalyst stores, to get the newly added feature, simply rebase the existing code with the new release code. The files to be rebased for this change to be applied are: + - core/messages/en.json + - core/app/[locale]/(default)/product/[slug]/page-data.ts + - core/app/[locale]/(default)/product/[slug]/page.tsx + - core/app/[locale]/(default)/product/[slug]/\_components/product-viewed/fragment.ts + - core/vibes/soul/sections/product-detail/index.tsx + - core/vibes/soul/sections/product-detail/product-detail-form.tsx + +### Patch Changes + +- [#2798](https://github.com/bigcommerce/catalyst/pull/2798) [`1632b19`](https://github.com/bigcommerce/catalyst/commit/1632b199048b8e1fcab961c59603e06a926c86d4) Thanks [@jordanarldt](https://github.com/jordanarldt)! - Update /login/token route error handling and messaging + + ## Migration steps + + ### 1. Add `invalidToken` translation key to the `Auth.Login` namespace: + + ```json + "invalidToken": "Your login link is invalid or has expired. Please try logging in again.", + ``` + + ### 2. In `core/app/[locale]/(default)/(auth)/login/token/[token]/route.ts`, add a `console.error` in the `catch` block to log the error details: + + ```typescript + } catch (error) { + // eslint-disable-next-line no-console + console.error(error); + + // ... + } + ``` + + ### 3. In `core/app/[locale]/(default)/(auth)/login/page.tsx`, add `error` prop to searchParams and pass it down into the `SignInSection` component: + + ```typescript + export default async function Login({ params, searchParams }: Props) { + const { locale } = await params; + const { redirectTo = '/account/orders', error } = await searchParams; + + setRequestLocale(locale); + + const t = await getTranslations('Auth.Login'); + const vanityUrl = buildConfig.get('urls').vanityUrl; + const redirectUrl = new URL(redirectTo, vanityUrl); + const redirectTarget = redirectUrl.pathname + redirectUrl.search; + const tokenErrorMessage = error === 'InvalidToken' ? t('invalidToken') : undefined; + + return ( + <> + + { + // If the form errors change when an "error" search param is in the URL, + // the search param should be removed to prevent showing stale errors. + if (form.errors) { + const url = new URL(window.location.href); + + if (url.searchParams.has('error')) { + url.searchParams.delete('error'); + window.history.replaceState({}, '', url.toString()); + } + } + }, [form.errors]); + + const formErrors = () => { + // Form errors should take precedence over the error prop that is passed in. + // This ensures that the most recent errors are displayed to avoid confusion. + if (form.errors) { + return form.errors; + } + + if (error) { + return [error]; + } + + return []; + }; + + return ( +
+ // ... + {submitLabel} + {formErrors().map((err, index) => ( + + {err} + + ))} +
+ ); + } + ``` + + ### 6. Copy all changes in the `core/tests` directory + +- [#2757](https://github.com/bigcommerce/catalyst/pull/2757) [`3f0fbb9`](https://github.com/bigcommerce/catalyst/commit/3f0fbb97035dd457ab388815991b4e9664685b2b) Thanks [@matthewvolk](https://github.com/matthewvolk)! - Passes `formButtonLabel` from `Reviews` to `ReviewsEmptyState` (was missing) and sets a default value for `formButtonLabel` + +- [#2800](https://github.com/bigcommerce/catalyst/pull/2800) [`57a3527`](https://github.com/bigcommerce/catalyst/commit/57a35277d86751ba1882b2b3086cf787d73a2748) Thanks [@jordanarldt](https://github.com/jordanarldt)! - Remove "Exclusive Offers" field temporarily. Currently, the field is not fully implemented in GraphQL, so it may be misleading to display it on the storefront if it's not actually doing anything when registering a customer. + + Once the Register Customer operation takes this field into account, we can display it again. + + ## Migration + + Update `core/app/[locale]/(default)/(auth)/register/page.tsx` and add the function: + + ```ts + // There is currently a GraphQL gap where the "Exclusive Offers" field isn't accounted for + // during customer registration, so the field should not be shown on the Catalyst storefront until it is hooked up. + function removeExlusiveOffersField(field: Field | Field[]): boolean { + if (Array.isArray(field)) { + // Exclusive offers field will always have ID '25', since it is made upon store creation and is also read-only. + return !field.some((f) => f.id === '25'); + } + + return field.id !== '25'; + } + ``` + + Then, add the following code at the end of the `const fields` declaration: + + ```ts + }) + .filter(exists) + .filter(removeExlusiveOffersField); // <--- + ``` + +- [#2782](https://github.com/bigcommerce/catalyst/pull/2782) [`abcb16a`](https://github.com/bigcommerce/catalyst/commit/abcb16a880fcfb09f5ea352fb2e4168c13679edb) Thanks [@jorgemoya](https://github.com/jorgemoya)! - Add missing check for optional text field in `core/vibes/soul/form/dynamic-form/schema.ts`. + + ## Migration + + Add `if (field.required !== true) fieldSchema = fieldSchema.optional();` to `text` case in `core/vibes/soul/form/dynamic-form/schema.ts`: + + ```typescript + case 'text': + fieldSchema = z.string(); + + if (field.pattern != null) { + fieldSchema = fieldSchema.regex(new RegExp(field.pattern), { + message: 'Invalid format.', + }); + } + + if (field.required !== true) fieldSchema = fieldSchema.optional(); + + break; + ``` + +- [#2750](https://github.com/bigcommerce/catalyst/pull/2750) [`c22f0e8`](https://github.com/bigcommerce/catalyst/commit/c22f0e889e96d23a972fcd9a702ec137bdd3a800) Thanks [@jorgemoya](https://github.com/jorgemoya)! - Improved login error handling to display a custom error message when BigCommerce indicates a password reset is required, instead of showing a generic error message. + + ## What's Fixed + + When attempting to log in with an account that requires a password reset, users now see an informative error message: "Password reset required. Please check your email for instructions to reset your password." + + **Before**: Generic "something went wrong" error message + **After**: Clear error message explaining the password reset requirement + + ## Migration + + ### Step 1: Update Translation Files + + Add this translation key to your locale files (e.g., `core/messages/en.json`): + + ```json + { + "Auth": { + "Login": { + "passwordResetRequired": "Password reset required. Please check your email for instructions to reset your password." + } + } + } + ``` + + Repeat for all supported locales if you maintain custom translations. + + ### Step 2: Update Login Server Action + + In your login server action (e.g., `core/app/[locale]/(default)/(auth)/login/_actions/login.ts`): + + Add the password reset error handling block: + + ```typescript + if ( + error instanceof AuthError && + error.type === 'CallbackRouteError' && + error.cause && + error.cause.err instanceof BigCommerceGQLError && + error.cause.err.message.includes('Reset password"') + ) { + return submission.reply({ formErrors: [t('passwordResetRequired')] }); + } + ``` + + This should be placed in your error handling, before the generic "Invalid credentials" check. + +- [#2802](https://github.com/bigcommerce/catalyst/pull/2802) [`1bff17d`](https://github.com/bigcommerce/catalyst/commit/1bff17deb3d0af15bd7c06324b0723324770943b) Thanks [@bc-svc-local](https://github.com/bc-svc-local)! - Update translations. + +- [#2780](https://github.com/bigcommerce/catalyst/pull/2780) [`9cc6786`](https://github.com/bigcommerce/catalyst/commit/9cc67865c74ff04127e42fb3bb00920f708151c4) Thanks [@bc-svc-local](https://github.com/bc-svc-local)! - Update translations. + +- [#2794](https://github.com/bigcommerce/catalyst/pull/2794) [`75b0a1a`](https://github.com/bigcommerce/catalyst/commit/75b0a1ad296f6376ee977394ea2ae2dcd290e2c5) Thanks [@bc-svc-local](https://github.com/bc-svc-local)! - Update translations. + +- [#2799](https://github.com/bigcommerce/catalyst/pull/2799) [`e3194e9`](https://github.com/bigcommerce/catalyst/commit/e3194e9546e3780f51d3703aa621ec7ca5225ade) Thanks [@bc-svc-local](https://github.com/bc-svc-local)! - Update translations. + ## 1.3.7 ### Patch Changes diff --git a/core/package.json b/core/package.json index 7c2299174b..3330a33aa0 100644 --- a/core/package.json +++ b/core/package.json @@ -1,7 +1,7 @@ { "name": "@bigcommerce/catalyst-core", "description": "BigCommerce Catalyst is a Next.js starter kit for building headless BigCommerce storefronts.", - "version": "1.3.7", + "version": "1.4.0", "private": true, "scripts": { "dev": "npm run generate && next dev",