Skip to content

feat(common): B2B-4938 update account settings via native BC Storefro…#960

Draft
bc-arezoo wants to merge 13 commits into
mainfrom
B2B-4938-account-settings-native-mutations
Draft

feat(common): B2B-4938 update account settings via native BC Storefro…#960
bc-arezoo wants to merge 13 commits into
mainfrom
B2B-4938-account-settings-native-mutations

Conversation

@bc-arezoo

@bc-arezoo bc-arezoo commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Jira: B2B-4938

What/Why?

Migrates account-settings updates from the legacy name-based b2b middleware mutations to the native BC Storefront GraphQL mutations, behind the PROJECT-7920.use_bc_account_settings flag:

  • BC customers → customer.updateCustomer; B2B company users → company.updateCompanyUser.
  • Form fields are sent as entityId-keyed typed groups. The fielconfig field_ fieldId; choice/checkbox option ids come fromsite.settings.formFields.customer.
  • Only changed custom fields are sent (normalized comparison), d now clears it.
  • reCaptcha token wiring is required for customer.updateCustomer and is shared via a new useStorefrontCaptcha hook (also adopted by ForgotPassword).
  • Flag off → unchanged legacy path.

Rollout/Rollback

Gated by the PROJECT-7920.use_bc_account_settings LaunchDarkly e flag; roll back by disabling it (falls straight back to thelegacy middleware path). Code-only change, no data migrations.

Testing

  • Unit + integration tests added/updated (utils.test.ts, index.test.tsx) — 41 utils + 5 SF-GQL integration tests covering both native paths, change detection, clearing, and entityId resolution. Full suite green;
  • Manual (flag on): BC customer and B2B company user — edited scalar fields, custom text/number fields, and password; confirmed the updateCustomer / updateCompanyUser payloads carry the expected formFields keyed
  • Manual (flag off): confirmed the legacy updateBCAccountSettings / updateB2BAccountSettings path is unchanged.

Known limitation: choice/checkbox custom fields require site.settings.formFields.customer, which currently returns empty through the b2b proxy; such edits surface an error rather than saving silently.


Note

Medium Risk
Touches customer profile, password, and B2B user updates with new payload shapes and reCaptcha gating; mitigated by feature flag rollback and loud failures for unmappable fields.

Overview
With PROJECT-7920.use_bc_account_settings on, account saves go through native Storefront GraphQL instead of legacy B2B middleware: BC customers use customer.updateCustomer (optional reCaptcha) plus customer.changePassword; B2B company users use company.updateCompanyUser.

Custom fields are sent only when changed, mapped into entityId-keyed typed formFields (and B2B contact-group name-keyed extraFields). site.settings.formFields.customer supplies entityIds and choice options; edits that cannot be encoded are rejected rather than dropped silently.

A shared useStorefrontCaptcha hook loads reCaptcha config for BC profile updates and Forgot Password. Flag off keeps the existing middleware update path.

Reviewed by Cursor Bugbot for commit 623b2c6. Bugbot is set up for automated code reviews on this repo. Configure here.

bc-arezoo and others added 3 commits July 8, 2026 09:12
…nt mutations

Behind the PROJECT-7920.use_bc_account_settings flag, BC customers now save
through customer.updateCustomer and B2B company users through
company.updateCompanyUser, replacing the legacy name-based middleware mutations.

- Build entityId-keyed formFields, resolving fieldEntityId from the field_<id>
  config fieldId and choice option ids from site.settings.formFields.customer.
- Send only changed custom fields (normalized comparison) and allow clearing
  empty text/date fields; wire reCaptcha via a shared useStorefrontCaptcha hook
  (also adopted by ForgotPassword).
- Fall back to the legacy path when the flag is off.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ests

- Stop exporting CHOICE_FIELD_TYPES; it's only used internally by
  fieldTypeNeedsOptions.
- Scope the native form-field tests to their own render container via
  within(result.container) so a leaked prior render can't cause a
  "found multiple elements" match under full-suite runs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ings tests

useStorefrontCaptcha issues an anonymous reCaptcha-config query that MSW can't
intercept; the resulting console.warn tripped fail-on-console and the failing
tests leaked their DOM (causing "found multiple elements" in later tests).
Mock getStorefrontToken so no request is made and reCaptcha stays disabled.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@bc-arezoo
bc-arezoo marked this pull request as ready for review July 8, 2026 00:00
@bc-arezoo
bc-arezoo requested a review from a team as a code owner July 8, 2026 00:00
Comment thread apps/storefront/src/pages/AccountSetting/index.tsx Outdated
Comment thread apps/storefront/src/pages/AccountSetting/index.tsx
bc-arezoo and others added 2 commits July 8, 2026 10:07
Company users never loaded site.settings.formFields.customer, so
buildUpdateCompanyUserInput ran without definitions and every choice/checkbox
edit hit the empty-definitions guard or couldn't map option labels to entity
ids. Fetch the definitions for both user types (in parallel with the account
read) and pass them into buildUpdateCompanyUserInput, matching the BC path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The native path called the update mutation and reported success even when
buildFormFieldsInput dropped every changed field (unmapped choice option,
cleared number/checkbox, or missing entityId), so the user saw "saved" while
the edit was silently lost. buildFormFieldsInput now also reports the
unsendable fields; the submit handler surfaces an error and aborts if any
changed field can't be represented, replacing the narrower empty-definitions
choice guard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread apps/storefront/src/pages/AccountSetting/index.tsx Outdated
Comment thread apps/storefront/src/pages/AccountSetting/index.tsx
Comment thread apps/storefront/src/pages/AccountSetting/utils.ts
…d checkbox handling

- dispatchUpdate: catch rejections from updateCustomer/updateCompanyUser so a
  network/proxy failure surfaces an error snackbar instead of throwing past the
  success flow.
- Block the BC submit until the reCaptcha config has loaded (useStorefrontCaptcha
  now exposes isCaptchaConfigLoading) so a customer can't submit without a token
  when the storefront requires one.
- Mark a checkbox unsendable when any selected label fails to resolve to an
  option entityId, so getUnsendableFormFields fails loudly instead of sending a
  partial selection.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread apps/storefront/src/pages/AccountSetting/utils.ts
Comment thread apps/storefront/src/pages/AccountSetting/utils.ts
Comment thread apps/storefront/src/pages/AccountSetting/index.tsx
}

export interface CustomerFormFieldDefinition {
__typename: string;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Question]
is __typename is required ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No, removed

Comment thread apps/storefront/src/pages/AccountSetting/index.tsx
…no-op logout

buildUpdateCustomerInput never emitted the password, so a BC customer's password
change was dropped while the flow still logged them out — appearing to succeed
without changing the password. customer.updateCustomer takes the password as a
form field, so emit it under the Password field's entityId (from the
definitions), and bail with an error before the logout-on-password-change when
that entityId can't be resolved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
name: string;
value: any;
fieldType?: string;
fieldEntityId?: number;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yes, the flow is: collectChangedFormFields parses it from the config fieldId → carries it on the BcFormFieldsProps entry → buildFormFieldsInput consumes it to build customer.updateCustomer / company.updateCompanyUser input.

Comment thread apps/storefront/src/pages/AccountSetting/index.tsx
…ce/date support

- Fetch customer form-field definitions with the correct BC schema types
  (Picklist/RadioButtons/Checkboxes options) so choice/checkbox option ids
  resolve; fetch whenever any custom field exists (also feeds the entityId
  label-fallback for non-`field_<id>` fields).
- Don't let a cleared/unresolvable field abort the whole save: blank
  number/date/dropdown are skipped; representable clears (text '', checkbox [])
  are sent; only genuinely unresolvable non-empty fields fail loudly.
- Compare dates on the date-only portion so an untouched date isn't re-sent, and
  emit dates as UTC-midnight ISO without local-timezone shift.
- Route BC password changes through customer.changePassword (password-first,
  captcha gates checked up front, force re-login if the profile update then
  fails); add company.updateCompanyUser name-keyed extraFields.
- Stop leaking fieldType into the legacy mutation; drop the dead passwords group;
  name the contact-group id (CONTACT_GROUP_ID).
- Tests: buildFormFieldsInput {formFields, unsendable}, clear/skip behavior,
  date change-detection, and a BC changePassword integration test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread apps/storefront/src/pages/AccountSetting/index.tsx Outdated
Comment thread apps/storefront/src/shared/service/bc/graphql/accountSetting.ts Outdated
…field definitions

Nothing reads __typename on CustomerFormFieldDefinition (the password lookup that
used it was replaced by customer.changePassword), so remove it from the
CustomerFormFieldSettings query and the type.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread apps/storefront/src/pages/AccountSetting/utils.ts
…word ordering

- Key custom form fields by bcLabel || label. getAccountFormFields leaves bcLabel
  empty when the config supplies only labelName (kept on `label`), so matching on
  bcLabel alone broke change detection, clears, and the definition entityId
  fallback; the same fallback is applied to the init default population.
- BC dispatch: check the reCaptcha gates before the password change and force a
  re-login if the profile update fails after the password already changed, so a
  gate/profile failure can't leave a committed password with an out-of-sync
  session. Password stays first to fail fast on a wrong current password.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread apps/storefront/src/pages/AccountSetting/utils.ts Outdated
Reorder the BC account-settings save so profile details are updated first
and the password only changes if that succeeds; if the details update fails
we return early and never commit the password. Also route company-user extra
fields through a reusable buildExtraFieldsInput that flags array-valued
(checkbox) fields as unsendable so the save fails loudly instead of dropping.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread apps/storefront/src/pages/AccountSetting/index.tsx
…dable

The BC formFields input has no empty representation for number/date/choice
fields, so a cleared one was silently dropped by buildFormFieldsInput. When
the clear was the only edit, the save reported a false success ("no edits")
without saving. Flag these clears as unsendable so the existing guard in
AccountSetting surfaces an error instead, matching the module's fail-loudly
handling of unmapped choices, missing entityIds, and partial checkboxes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@bc-arezoo

Copy link
Copy Markdown
Contributor Author

@bc-vasu As discussed yesterday, can you please confirm the reCaptcha changes with @bc-amandalan and how we need to move forward?

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 3b5015c. Configure here.

}
}
}
}`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

updateCustomer ignores mutation errors

Medium Severity

The native BC customer profile save only treats top-level GraphQL errors as failure. The UpdateCustomer mutation does not request or check updateCustomer result errors, unlike changePassword and updateCompanyUser in the same change. Validation or business failures returned in the mutation payload can be treated as success.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3b5015c. Configure here.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@bc-vasu
bc-vasu marked this pull request as draft July 10, 2026 04:14
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.

4 participants