Skip to content

feat: display and edit sponsorship tiers in forms list view table#827

Open
priscila-moneo wants to merge 1 commit intomasterfrom
feature/show-level-form-see-tiers
Open

feat: display and edit sponsorship tiers in forms list view table#827
priscila-moneo wants to merge 1 commit intomasterfrom
feature/show-level-form-see-tiers

Conversation

@priscila-moneo
Copy link

@priscila-moneo priscila-moneo commented Mar 17, 2026

ref: https://app.clickup.com/t/86b8t9qw1

image image image

Summary by CodeRabbit

  • New Features

    • Inline tier editing for sponsor forms with a tiers column and editable dropdown.
    • Dropdown exposes blur/open/close callbacks for richer interaction.
    • Added localized labels: "Tiers" and "Manage Items".
    • Sponsor forms now surface sponsorship type data for display/editing.
  • Bug Fixes

    • Prevent snackbar from opening with empty HTML content.
    • More defensive date handling for form templates.
    • Promise rejections now propagate instead of being silently swallowed.

@coderabbitai
Copy link

coderabbitai bot commented Mar 17, 2026

📝 Walkthrough

Walkthrough

Adds inline sponsorship-tier editing to sponsor forms, includes sponsorship_types and apply_to_all_types in fetches and reducer, improves defensive date/meta_field normalization and propagates promise rejections, exposes dropdown lifecycle callbacks, tightens snackbar HTML checks, and adds two i18n keys.

Changes

Cohort / File(s) Summary
Sponsor Forms Actions
src/actions/sponsor-forms-actions.js
Fetch now includes sponsorship_types and apply_to_all_types; normalizeFormTemplate defensively handles opens_at/expires_at, sponsorship_types, and meta_fields; update/clone/save chains now propagate promise rejections.
Sponsor Forms List UI
src/pages/sponsors/sponsor-forms-list-page/index.js
Adds inline tiers column with editable DropdownCheckbox, normalizeTiers helper, local edit state, calls updateFormTemplate and refreshes list; component now expects sponsorships prop.
Reducer: Sponsor Forms List
src/reducers/sponsors/sponsor-forms-list-reducer.js
Transforms received forms to include computed sponsorship_types (sets ["all"] when apply_to_all_types true or maps objects to ids).
Dropdown Checkbox Component
src/components/mui/dropdown-checkbox.js
Adds public props onBlur, onClose, onOpen, onCloseMenu; forwards events to underlying Select; implements exclusive "all" selection logic.
Snackbar Notification
src/components/mui/SnackbarNotification/index.js
Requires non-empty trimmed HTML for msgData.html and snackbarMessage.html; closes snackbar when content becomes empty.
Internationalization
src/i18n/en.json
Adds sponsor_forms.forms.tiers_column_label ("Tiers") and sponsor_forms.forms.manage_items_button ("Manage Items").

Sequence Diagram

sequenceDiagram
    actor User
    participant UI as SponsorFormsList
    participant Dropdown as DropdownCheckbox
    participant Action as updateFormTemplate
    participant API as Backend
    participant Reducer as SponsorFormsReducer
    participant Store as ReduxState

    User->>UI: Click tier label (enter edit)
    UI->>Dropdown: Open with current sponsorship_types
    User->>Dropdown: Select/deselect tiers (handles "all" logic)
    Dropdown->>UI: onClose / onCloseMenu callback with new value
    UI->>Action: Call updateFormTemplate(formId, {sponsorship_types, apply_to_all_types})
    Action->>API: PATCH form
    API-->>Action: Return updated form
    Action->>Reducer: Dispatch RECEIVE_SPONSOR_FORMS
    Reducer->>Store: Compute/attach sponsorship_types and update list
    Store-->>UI: State updated -> re-render tiers column
    UI-->>User: Show updated tiers
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly Related PRs

Suggested Reviewers

  • smarcet
  • martinquiroga-exo
  • tomrndom

Poem

🐰 I nibbled through tiers with care,

Clicking dropdowns in the air,
Dates and meta fields made neat,
Snackbars folded, selections sweet,
Forms now hop along with flair. ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: display and edit sponsorship tiers in forms list view table' accurately and clearly summarizes the main changes: adding tier display and inline editing functionality to the forms list table.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/show-level-form-see-tiers
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/actions/sponsor-forms-actions.js (1)

471-484: ⚠️ Potential issue | 🔴 Critical

Do not synthesize meta_fields: [] for partial form updates.

The new inline tier editor in src/pages/sponsors/sponsor-forms-list-page/index.js (Lines 194-198) updates a form with only tier fields. Defaulting missing meta_fields to [] turns that call into a destructive write and can wipe existing additional fields from the template.

🛡️ Proposed fix
-  const sponsorship_types = entity.sponsorship_types || [];
-  const meta_fields = Array.isArray(entity.meta_fields)
-    ? entity.meta_fields
-    : [];
+  const sponsorship_types = entity.sponsorship_types || [];
+  const hasMetaFields = Array.isArray(entity.meta_fields);

   normalizedEntity.apply_to_all_types = false;
   normalizedEntity.sponsorship_types = sponsorship_types;
@@
-  normalizedEntity.meta_fields = meta_fields.filter((mf) => !!mf.name);
+  if (hasMetaFields) {
+    normalizedEntity.meta_fields = entity.meta_fields.filter((mf) => !!mf.name);
+  } else {
+    delete normalizedEntity.meta_fields;
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/actions/sponsor-forms-actions.js` around lines 471 - 484, The code
currently synthesizes meta_fields = [] when entity.meta_fields is missing,
causing partial updates to wipe existing fields; change the logic to only assign
normalizedEntity.meta_fields when entity actually has a meta_fields property.
Specifically, remove the unconditional defaulting "const meta_fields =
Array.isArray(entity.meta_fields) ? entity.meta_fields : []" and instead check
"if (Object.prototype.hasOwnProperty.call(entity, 'meta_fields')) {
normalizedEntity.meta_fields = Array.isArray(entity.meta_fields) ?
entity.meta_fields.filter(mf => !!mf.name) : undefined }" so that
normalizedEntity.meta_fields is not written at all for partial updates that omit
meta_fields (referencing normalizedEntity, entity, and meta_fields/filter in the
diff).
src/pages/sponsors/sponsor-forms-list-page/index.js (1)

53-68: ⚠️ Potential issue | 🟠 Major

Fetch sponsorship options before using the new tiers column.

This component now relies on sponsorships.items for both label rendering and inline editing, but it never populates that slice. Since sponsorships.items starts empty in src/reducers/sponsors/sponsor-forms-list-reducer.js (Lines 48-53), the new tiers UI comes up with raw ids at best and an empty dropdown at worst.

🔧 Proposed fix
 import {
   archiveSponsorForm,
   getSponsorForm,
   getSponsorForms,
+  getSponsorships,
   unarchiveSponsorForm,
   deleteSponsorForm,
   updateFormTemplate
 } from "../../../actions/sponsor-forms-actions";
@@
   totalCount,
   getSponsorForms,
+  getSponsorships,
   getSponsorForm,
   archiveSponsorForm,
   unarchiveSponsorForm,
   deleteSponsorForm,
   updateFormTemplate,
   sponsorships
 }) => {
@@
   useEffect(() => {
     getSponsorForms();
-  }, []);
+    getSponsorships();
+  }, [getSponsorForms, getSponsorships]);
@@
 export default connect(mapStateToProps, {
   getSponsorForms,
+  getSponsorships,
   getSponsorForm,
   archiveSponsorForm,
   unarchiveSponsorForm,
   deleteSponsorForm,
   updateFormTemplate
 })(SponsorFormsListPage);

If getSponsorships is paginated, make sure this load fetches the full tier set; otherwise some ids still won’t resolve.

Also applies to: 216-289

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/sponsors/sponsor-forms-list-page/index.js` around lines 53 - 68,
The SponsorFormsListPage now renders and edits tiers using sponsorships.items
but never loads them; call the action that populates that slice (e.g.,
getSponsorships) when the component mounts or before rendering the tiers UI so
sponsorships.items contains the full set, and guard the tiers renderer/editors
to wait for sponsorships.items to be populated (or show a loading state). If
getSponsorships is paginated, ensure you request the full set (e.g., fetch all
pages or use a non-paginated endpoint) so IDs resolve correctly; update the
component’s useEffect (or lifecycle) where getSponsorForms/getSponsorForm is
called to also dispatch getSponsorships and rely on sponsorships.items for label
lookup and dropdown options.
src/components/mui/dropdown-checkbox.js (1)

25-42: ⚠️ Potential issue | 🟡 Minor

Normalize event.target.value before using array methods.

MUI Select with multiple can emit a string value via browser autofill. The current code will fail on line 35 when .filter() is called on a string, and .includes() on line 30 will incorrectly match substrings rather than array elements.

♻️ Proposed fix
   const handleChange = (ev) => {
-    const selected = ev.target.value;
+    const rawValue = ev.target.value;
+    const selected = Array.isArray(rawValue)
+      ? rawValue
+      : typeof rawValue === "string"
+      ? rawValue.split(",")
+      : [];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/mui/dropdown-checkbox.js` around lines 25 - 42, In
handleChange normalize ev.target.value (and the existing value) to an array
before calling .includes() or .filter(): inside handleChange (where selected is
defined) coerce selected to an array (e.g. if typeof ev.target.value ===
'string' convert to ev.target.value.split(',') or wrap in [ev.target.value];
likewise normalize the current value used in comparisons) so subsequent checks
like selected.includes("all") and selected.filter(...) operate on arrays, then
call onChange with the normalized arrays as before.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/actions/sponsor-forms-actions.js`:
- Around line 455-470: The parsing of opens_at and expires_at uses moment(value,
summitTZ) which treats summitTZ as a format string; update both occurrences so
non-number timestamps are parsed with moment.tz(entity.opens_at,
summitTZ).unix() and moment.tz(entity.expires_at, summitTZ).unix() respectively,
keeping the existing typeof number branch and the delete behavior; modify the
lines that assign normalizedEntity.opens_at and normalizedEntity.expires_at to
call moment.tz(...) instead of moment(...), referencing normalizedEntity,
entity.opens_at, entity.expires_at and summitTZ.

In `@src/reducers/sponsors/sponsor-forms-list-reducer.js`:
- Around line 94-110: The reducer currently assigns sponsorship_types as the
expanded objects instead of scalar ids; update the mapping inside the
sponsorForms creation (the payload.response.data.map callback) so that
sponsorship_types is normalized to an array of scalar ids (e.g.,
a.sponsorship_types.map(t => t.id) when a.sponsorship_types is an array) and
keep the "all" behavior for a.apply_to_all_types; ensure the returned object
uses this normalized sponsorship_types so the tiers lookup in the UI (which
expects ids) receives scalar ids.

---

Outside diff comments:
In `@src/actions/sponsor-forms-actions.js`:
- Around line 471-484: The code currently synthesizes meta_fields = [] when
entity.meta_fields is missing, causing partial updates to wipe existing fields;
change the logic to only assign normalizedEntity.meta_fields when entity
actually has a meta_fields property. Specifically, remove the unconditional
defaulting "const meta_fields = Array.isArray(entity.meta_fields) ?
entity.meta_fields : []" and instead check "if
(Object.prototype.hasOwnProperty.call(entity, 'meta_fields')) {
normalizedEntity.meta_fields = Array.isArray(entity.meta_fields) ?
entity.meta_fields.filter(mf => !!mf.name) : undefined }" so that
normalizedEntity.meta_fields is not written at all for partial updates that omit
meta_fields (referencing normalizedEntity, entity, and meta_fields/filter in the
diff).

In `@src/components/mui/dropdown-checkbox.js`:
- Around line 25-42: In handleChange normalize ev.target.value (and the existing
value) to an array before calling .includes() or .filter(): inside handleChange
(where selected is defined) coerce selected to an array (e.g. if typeof
ev.target.value === 'string' convert to ev.target.value.split(',') or wrap in
[ev.target.value]; likewise normalize the current value used in comparisons) so
subsequent checks like selected.includes("all") and selected.filter(...) operate
on arrays, then call onChange with the normalized arrays as before.

In `@src/pages/sponsors/sponsor-forms-list-page/index.js`:
- Around line 53-68: The SponsorFormsListPage now renders and edits tiers using
sponsorships.items but never loads them; call the action that populates that
slice (e.g., getSponsorships) when the component mounts or before rendering the
tiers UI so sponsorships.items contains the full set, and guard the tiers
renderer/editors to wait for sponsorships.items to be populated (or show a
loading state). If getSponsorships is paginated, ensure you request the full set
(e.g., fetch all pages or use a non-paginated endpoint) so IDs resolve
correctly; update the component’s useEffect (or lifecycle) where
getSponsorForms/getSponsorForm is called to also dispatch getSponsorships and
rely on sponsorships.items for label lookup and dropdown options.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3a6a045d-c905-42c1-aece-9d9586ad55cd

📥 Commits

Reviewing files that changed from the base of the PR and between 6152c8e and bb758a2.

📒 Files selected for processing (6)
  • src/actions/sponsor-forms-actions.js
  • src/components/mui/SnackbarNotification/index.js
  • src/components/mui/dropdown-checkbox.js
  • src/i18n/en.json
  • src/pages/sponsors/sponsor-forms-list-page/index.js
  • src/reducers/sponsors/sponsor-forms-list-reducer.js

@priscila-moneo priscila-moneo changed the title feat: show level form see tiers feat: display and edit sponsorship tiers in forms list view table Mar 17, 2026
@priscila-moneo priscila-moneo force-pushed the feature/show-level-form-see-tiers branch from 6484db0 to f36c334 Compare March 17, 2026 19:45
@priscila-moneo priscila-moneo force-pushed the feature/show-level-form-see-tiers branch from f36c334 to 7089c0f Compare March 17, 2026 20:27
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/pages/sponsors/sponsor-forms-list-page/index.js (1)

181-183: Use order-insensitive equality for tier sets.

Current comparison treats [1,2] and [2,1] as different and can trigger unnecessary updates.

Proposed adjustment
-  const arraysEqual = (a, b) =>
-    a.length === b.length && a.every((v, i) => v === b[i]);
+  const arraysEqual = (a, b) => {
+    if (a.length !== b.length) return false;
+    const as = [...a].map(String).sort();
+    const bs = [...b].map(String).sort();
+    return as.every((v, i) => v === bs[i]);
+  };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/sponsors/sponsor-forms-list-page/index.js` around lines 181 - 183,
The current arraysEqual helper used by handleTiersSave is order-sensitive and
will treat [1,2] and [2,1] as different; change arraysEqual to perform
order-insensitive comparison (e.g., convert both inputs to Sets and compare
sizes and that every value in one Set exists in the other, or sort both arrays
of primitive ids before comparing) and ensure handleTiersSave uses the updated
arraysEqual so tier sets are detected correctly without triggering unnecessary
updates.
src/actions/sponsor-forms-actions.js (1)

447-447: Redundant reject wrapper in catch.

.catch((e) => Promise.reject(e)) is equivalent to rethrowing; it adds noise without behavior gain.

Proposed simplification
-    .catch((e) => Promise.reject(e))
+    .catch((e) => {
+      throw e;
+    })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/actions/sponsor-forms-actions.js` at line 447, The .catch((e) =>
Promise.reject(e)) wrapper is redundant—remove this catch (or replace it with a
simple rethrow if you need to run side-effects) so the original Promise
rejection propagates naturally; locate the .catch((e) => Promise.reject(e))
occurrence in src/actions/sponsor-forms-actions.js and delete that call (or
change to .catch(e => { /* optional logging */ throw e }) if you want to log
before rethrowing).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/actions/sponsor-forms-actions.js`:
- Around line 455-470: The normalize logic for opens_at and expires_at treats
empty strings as present and converts them; update the guards in the block
handling entity.opens_at and entity.expires_at so you only normalize when the
value is not undefined/null/empty string (e.g., check entity.opens_at !==
undefined && entity.opens_at !== null && entity.opens_at !== "" and same for
entity.expires_at), otherwise delete normalizedEntity.opens_at /
normalizedEntity.expires_at; keep using typeof entity.* === "number" ? ... :
moment.tz(entity.*, summitTZ).unix() for conversion.

In `@src/pages/sponsors/sponsor-forms-list-page/index.js`:
- Around line 44-51: normalizeTiers currently coerces an empty tiers array into
["all"], which causes untouched/legacy empty rows to be treated as
apply_to_all_types=true when saved; change normalizeTiers so that when arr is an
empty array it returns [] (do not return ["all"]), and update any save-path
logic that checks for ["all"] (the code that maps tiers into apply_to_all_types)
to treat an empty array as the absence of selection rather than "all". Ensure
callers of normalizeTiers still handle both arrays of ids and arrays of objects
(preserve the arr.map((t) => t.id) branch) but remove the empty-array -> ["all"]
coercion.

---

Nitpick comments:
In `@src/actions/sponsor-forms-actions.js`:
- Line 447: The .catch((e) => Promise.reject(e)) wrapper is redundant—remove
this catch (or replace it with a simple rethrow if you need to run side-effects)
so the original Promise rejection propagates naturally; locate the .catch((e) =>
Promise.reject(e)) occurrence in src/actions/sponsor-forms-actions.js and delete
that call (or change to .catch(e => { /* optional logging */ throw e }) if you
want to log before rethrowing).

In `@src/pages/sponsors/sponsor-forms-list-page/index.js`:
- Around line 181-183: The current arraysEqual helper used by handleTiersSave is
order-sensitive and will treat [1,2] and [2,1] as different; change arraysEqual
to perform order-insensitive comparison (e.g., convert both inputs to Sets and
compare sizes and that every value in one Set exists in the other, or sort both
arrays of primitive ids before comparing) and ensure handleTiersSave uses the
updated arraysEqual so tier sets are detected correctly without triggering
unnecessary updates.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 304a11dd-ae82-443a-a090-142f3a068270

📥 Commits

Reviewing files that changed from the base of the PR and between bb758a2 and 7089c0f.

📒 Files selected for processing (6)
  • src/actions/sponsor-forms-actions.js
  • src/components/mui/SnackbarNotification/index.js
  • src/components/mui/dropdown-checkbox.js
  • src/i18n/en.json
  • src/pages/sponsors/sponsor-forms-list-page/index.js
  • src/reducers/sponsors/sponsor-forms-list-reducer.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/components/mui/SnackbarNotification/index.js
  • src/reducers/sponsors/sponsor-forms-list-reducer.js

Comment on lines +455 to +470
if (entity.opens_at !== undefined && entity.opens_at !== null) {
normalizedEntity.opens_at =
typeof entity.opens_at === "number"
? entity.opens_at
: moment.tz(entity.opens_at, summitTZ).unix();
} else {
delete normalizedEntity.opens_at;
}
if (entity.expires_at !== undefined && entity.expires_at !== null) {
normalizedEntity.expires_at =
typeof entity.expires_at === "number"
? entity.expires_at
: moment.tz(entity.expires_at, summitTZ).unix();
} else {
delete normalizedEntity.expires_at;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard empty date strings before conversion.

opens_at/expires_at currently treat "" as present (Line 455 and Line 463) and still normalize it. This can push invalid date values in update payloads when users clear date fields.

Proposed fix
-  if (entity.opens_at !== undefined && entity.opens_at !== null) {
+  if (
+    entity.opens_at !== undefined &&
+    entity.opens_at !== null &&
+    entity.opens_at !== ""
+  ) {
     normalizedEntity.opens_at =
       typeof entity.opens_at === "number"
         ? entity.opens_at
         : moment.tz(entity.opens_at, summitTZ).unix();
   } else {
     delete normalizedEntity.opens_at;
   }
-  if (entity.expires_at !== undefined && entity.expires_at !== null) {
+  if (
+    entity.expires_at !== undefined &&
+    entity.expires_at !== null &&
+    entity.expires_at !== ""
+  ) {
     normalizedEntity.expires_at =
       typeof entity.expires_at === "number"
         ? entity.expires_at
         : moment.tz(entity.expires_at, summitTZ).unix();
   } else {
     delete normalizedEntity.expires_at;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/actions/sponsor-forms-actions.js` around lines 455 - 470, The normalize
logic for opens_at and expires_at treats empty strings as present and converts
them; update the guards in the block handling entity.opens_at and
entity.expires_at so you only normalize when the value is not
undefined/null/empty string (e.g., check entity.opens_at !== undefined &&
entity.opens_at !== null && entity.opens_at !== "" and same for
entity.expires_at), otherwise delete normalizedEntity.opens_at /
normalizedEntity.expires_at; keep using typeof entity.* === "number" ? ... :
moment.tz(entity.*, summitTZ).unix() for conversion.

Comment on lines +44 to +51
const normalizeTiers = (arr) =>
Array.isArray(arr)
? arr.length === 0
? ["all"]
: typeof arr[0] === "object"
? arr.map((t) => t.id)
: arr
: [];
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don’t coerce empty tiers to ["all"] in normalization.

Line 47 changes an empty tiers array into "all". In the current save path (Lines 183-193), that can convert untouched legacy/empty rows into apply_to_all_types=true just by entering and closing edit mode.

Proposed fix
 const normalizeTiers = (arr) =>
   Array.isArray(arr)
-    ? arr.length === 0
-      ? ["all"]
+    ? arr.length === 0
+      ? []
       : typeof arr[0] === "object"
       ? arr.map((t) => t.id)
       : arr
     : [];

Also applies to: 183-193

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/sponsors/sponsor-forms-list-page/index.js` around lines 44 - 51,
normalizeTiers currently coerces an empty tiers array into ["all"], which causes
untouched/legacy empty rows to be treated as apply_to_all_types=true when saved;
change normalizeTiers so that when arr is an empty array it returns [] (do not
return ["all"]), and update any save-path logic that checks for ["all"] (the
code that maps tiers into apply_to_all_types) to treat an empty array as the
absence of selection rather than "all". Ensure callers of normalizeTiers still
handle both arrays of ids and arrays of objects (preserve the arr.map((t) =>
t.id) branch) but remove the empty-array -> ["all"] coercion.

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.

1 participant