fix: migrate payment profiles to MUI components#925
Conversation
Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>
📝 WalkthroughWalkthroughConsolidates payment profile and fee-type management into a single list page with a modal dialog, adds search/pagination state to Redux, standardizes snackbar-based API notifications, simplifies routing, and replaces several route-based pages with a unified UI component. ChangesPayment Profile Dialog-Based Management
Sequence DiagramsequenceDiagram
participant User
participant ListPage
participant Dialog
participant Redux
participant API
participant Snack as Snackbar
User->>ListPage: search / paginate / open edit
ListPage->>Redux: dispatch getPaymentProfiles / getPaymentProfile / getPaymentFeeTypes
Redux->>API: http requests
API-->>Redux: responses (with pagination)
Redux->>ListPage: update state
ListPage->>Dialog: open with entity & feeTypes
Dialog->>Redux: dispatch savePaymentProfile / savePaymentFeeType
Redux->>API: save request
API-->>Redux: success / error
Redux->>Snack: snackbarSuccessHandler / snackbarErrorHandler
Snack->>User: show notification
Redux->>ListPage: refresh list
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/reducers/payment_profiles/payment-profile-list-reducer.js (1)
43-45:⚠️ Potential issue | 🟠 Major | ⚡ Quick winPersist the requested
termandperPagein list state.Right now only
order/orderDirare stored here. After a search or rows-per-page change, Redux still holds the previousterm/perPage, so subsequent paging/sorting re-queries with stale values and drops the active filter/page size.Suggested fix
case REQUEST_PAYMENT_PROFILES: { - const { order, orderDir } = payload; - return { ...state, order, orderDir }; + const { term, page, perPage, order, orderDir } = payload; + return { + ...state, + term, + currentPage: page, + perPage, + order, + orderDir + }; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/reducers/payment_profiles/payment-profile-list-reducer.js` around lines 43 - 45, The REQUEST_PAYMENT_PROFILES case in payment-profile-list-reducer.js currently only persists order and orderDir; update it to also read term and perPage from the action payload and include them in the returned state (i.e., return { ...state, order, orderDir, term, perPage }) so searches and rows-per-page changes are stored on the list state; modify the REQUEST_PAYMENT_PROFILES branch where order/orderDir are extracted to also extract term and perPage from payload and add them to the new state object.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/pages/tickets/payment-profile/components/payment-profile-dialog.js`:
- Around line 315-354: The live_secret_key and test_secret_key fields are
rendered as plain text; change their MuiFormikTextField usage to mask values by
setting type="password" (for example add type="password" prop on the
MuiFormikTextField for name="live_secret_key" and name="test_secret_key") and
add a toggleable visibility affordance using an endAdornment
(IconButton/visibility icons) so admins can reveal values when needed; keep
existing props (required/fullWidth/variant) and ensure the visibility toggle
updates the input type and includes accessible labels.
- Around line 213-220: Remove the three focus-disabling props from the Dialog
(disableEnforceFocus, disableAutoFocus, disableRestoreFocus) so the modal
regains its focus trap and keyboard/screen-reader navigation; keep the Dialog
open/onClose handling (Dialog, handleClose) as-is. If a specific input or
control inside the dialog is causing focus issues, revert the global changes and
apply a targeted workaround to that control only (for example, render
problematic popovers/menus in a portal, use the control's built-in
disableAutoFocus or disableEnforceFocus flags, or adjust its focus handling)
rather than disabling focus management on the entire Dialog.
In `@src/pages/tickets/payment-profile/payment-profile-list-page.js`:
- Around line 100-104: The inline fee-type save handler handleSaveFeeType should
refresh the fee list but must not close the parent PaymentProfileDialog; remove
the setPaymentProfilePopup(false) call from handleSaveFeeType and keep calling
getPaymentFeeTypes(currentPaymentProfile.id) after savePaymentFeeType(entity)
resolves so the dialog stays open and shows refreshed data, and ensure you do
not trigger any additional unmount/reset logic that conflicts with
PaymentProfileDialog’s own then path.
---
Outside diff comments:
In `@src/reducers/payment_profiles/payment-profile-list-reducer.js`:
- Around line 43-45: The REQUEST_PAYMENT_PROFILES case in
payment-profile-list-reducer.js currently only persists order and orderDir;
update it to also read term and perPage from the action payload and include them
in the returned state (i.e., return { ...state, order, orderDir, term, perPage
}) so searches and rows-per-page changes are stored on the list state; modify
the REQUEST_PAYMENT_PROFILES branch where order/orderDir are extracted to also
extract term and perPage from payload and add them to the new state object.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 2529cc2e-39a0-40a8-a92f-524919ed0f65
📒 Files selected for processing (9)
src/actions/ticket-actions.jssrc/i18n/en.jsonsrc/layouts/payment-profile-layout.jssrc/pages/tickets/edit-payment-fee-type-page.jssrc/pages/tickets/edit-payment-profile-page.jssrc/pages/tickets/payment-profile-list-page.jssrc/pages/tickets/payment-profile/components/payment-profile-dialog.jssrc/pages/tickets/payment-profile/payment-profile-list-page.jssrc/reducers/payment_profiles/payment-profile-list-reducer.js
💤 Files with no reviewable changes (3)
- src/pages/tickets/edit-payment-fee-type-page.js
- src/pages/tickets/edit-payment-profile-page.js
- src/pages/tickets/payment-profile-list-page.js
| const handleSaveFeeType = (entity) => | ||
| savePaymentFeeType(entity).then(() => { | ||
| getPaymentFeeTypes(currentPaymentProfile.id); | ||
| setPaymentProfilePopup(false); | ||
| }); |
There was a problem hiding this comment.
Keep the payment-profile dialog open after saving a fee type.
This closes the entire parent dialog after an inline fee-type save, so users cannot review the refreshed fee list or continue editing the payment profile. It also leaves PaymentProfileDialog’s own then path resetting fee-type state after the parent has already unmounted it.
Suggested fix
const handleSaveFeeType = (entity) =>
savePaymentFeeType(entity).then(() => {
- getPaymentFeeTypes(currentPaymentProfile.id);
- setPaymentProfilePopup(false);
+ return getPaymentFeeTypes(currentPaymentProfile.id);
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const handleSaveFeeType = (entity) => | |
| savePaymentFeeType(entity).then(() => { | |
| getPaymentFeeTypes(currentPaymentProfile.id); | |
| setPaymentProfilePopup(false); | |
| }); | |
| const handleSaveFeeType = (entity) => | |
| savePaymentFeeType(entity).then(() => { | |
| return getPaymentFeeTypes(currentPaymentProfile.id); | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/pages/tickets/payment-profile/payment-profile-list-page.js` around lines
100 - 104, The inline fee-type save handler handleSaveFeeType should refresh the
fee list but must not close the parent PaymentProfileDialog; remove the
setPaymentProfilePopup(false) call from handleSaveFeeType and keep calling
getPaymentFeeTypes(currentPaymentProfile.id) after savePaymentFeeType(entity)
resolves so the dialog stays open and shows refreshed data, and ensure you do
not trigger any additional unmount/reset logic that conflicts with
PaymentProfileDialog’s own then path.
Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/pages/tickets/payment-profile/components/payment-profile-dialog.js (1)
588-600:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winFix PropTypes mismatch:
paymentFeeTypesis an object, not an array.The
paymentFeeTypesprop is passed as an object fromcurrentPaymentFeeListTypeState(defined insrc/reducers/payment_profiles/payment-fee-types-list-reducer.js) with propertiesorder,orderDir,totalPaymentFeeTypes, andpaymentFeeTypes. It is currently typed asPropTypes.array, which fails to validate the actual object structure. Additionally, no default value is provided, risking undefined property access.Suggested fix
PaymentProfileDialog.propTypes = { onClose: PropTypes.func.isRequired, onSave: PropTypes.func.isRequired, entity: PropTypes.object, - paymentFeeTypes: PropTypes.array, + paymentFeeTypes: PropTypes.shape({ + order: PropTypes.string, + orderDir: PropTypes.number, + totalPaymentFeeTypes: PropTypes.number, + paymentFeeTypes: PropTypes.arrayOf(PropTypes.object) + }), isSaving: PropTypes.bool, onSaveFeeType: PropTypes.func.isRequired, onDeleteFeeType: PropTypes.func.isRequired }; PaymentProfileDialog.defaultProps = { - entity: {} + entity: {}, + paymentFeeTypes: { + order: "id", + orderDir: 1, + totalPaymentFeeTypes: 0, + paymentFeeTypes: [] + } };🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/pages/tickets/payment-profile/components/payment-profile-dialog.js` around lines 588 - 600, PaymentProfileDialog's propTypes incorrectly declare paymentFeeTypes as PropTypes.array while the component receives an object (from currentPaymentFeeListTypeState) with keys order, orderDir, totalPaymentFeeTypes, and paymentFeeTypes; update PaymentProfileDialog.propTypes to use PropTypes.shape({ order: PropTypes.number, orderDir: PropTypes.string, totalPaymentFeeTypes: PropTypes.number, paymentFeeTypes: PropTypes.array }) (mark fields required as appropriate) and add a matching default in PaymentProfileDialog.defaultProps (e.g., an empty shape with sensible defaults) to avoid undefined access.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@src/pages/tickets/payment-profile/components/payment-profile-dialog.js`:
- Around line 588-600: PaymentProfileDialog's propTypes incorrectly declare
paymentFeeTypes as PropTypes.array while the component receives an object (from
currentPaymentFeeListTypeState) with keys order, orderDir, totalPaymentFeeTypes,
and paymentFeeTypes; update PaymentProfileDialog.propTypes to use
PropTypes.shape({ order: PropTypes.number, orderDir: PropTypes.string,
totalPaymentFeeTypes: PropTypes.number, paymentFeeTypes: PropTypes.array })
(mark fields required as appropriate) and add a matching default in
PaymentProfileDialog.defaultProps (e.g., an empty shape with sensible defaults)
to avoid undefined access.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: f2536394-67a8-4c22-af80-ae594c1a9dae
📒 Files selected for processing (2)
src/pages/tickets/payment-profile/components/payment-profile-dialog.jssrc/pages/tickets/payment-profile/payment-profile-list-page.js
|
@tomrndom there are a couple of ux issues at fee types ( pre existents) |
smarcet
left a comment
There was a problem hiding this comment.
Deep review — 8 findings (2 HIGH, 5 MEDIUM, 1 LOW) posted inline. Verdict: needs changes. Highlights: validation schema is commented out (HIGH); saving a fee type closes the entire profile dialog (HIGH); tests for the new dialog/list page and the new term filter are missing.
| test_secret_key: initialEntity?.test_secret_key || "", | ||
| test_publishable_key: initialEntity?.test_publishable_key || "" | ||
| }, | ||
| // validationSchema: yup.object().shape({ |
There was a problem hiding this comment.
🔴 [HIGH] Validation schema is commented out on the main profile form
Users can save a profile with empty application_type, provider, and (for Stripe) empty live keys — onSubmit runs unconditionally and the previous form's required-field guards are silently regressed.
Fix: Re-enable this Yup schema (or remove the commented block and add the live one) before merging.
| savePaymentFeeType(entity).then(() => { | ||
| getPaymentFeeTypes(currentPaymentProfile.id); | ||
| setPaymentProfilePopup(false); | ||
| }); |
There was a problem hiding this comment.
🔴 [HIGH] Saving a fee type closes the entire payment-profile dialog
setPaymentProfilePopup(false) here unmounts the parent dialog after every fee-type save, dropping any unsaved profile edits and forcing the user to re-open the profile.
Fix: After a fee-type save, only refresh getPaymentFeeTypes(currentPaymentProfile.id) and reset the inner fee-type form. Do NOT close the profile dialog — mirror handleDeleteFeeType below, which is correct.
Also: there is no .catch here, so the popup-dialog rule (.claude/rules/summit-admin-popup-dialog-pattern.md) — keep dialog open on error — is not honoured.
| setIsSaving(true); | ||
| savePaymentProfile(entity) | ||
| .then(() => { | ||
| getPaymentProfiles("", DEFAULT_CURRENT_PAGE, perPage); |
There was a problem hiding this comment.
🟡 [MEDIUM] Active search term is silently dropped on every refresh
handleSave (here), handleDelete (line 82), and the mount useEffect (line 56) all call getPaymentProfiles("", DEFAULT_CURRENT_PAGE, perPage) with an empty term. After saving or deleting a profile while a search filter is active, results jump back to the unfiltered first page with no UI cue and the SearchInput desyncs from the data.
Fix: Pass the persisted term (and order / orderDir) — or extract a refreshList() helper that reads them from props.
Also: handleSave has no .catch. Per .claude/rules/summit-admin-popup-dialog-pattern.md the dialog should stay open on error to preserve user input.
| const handleDeleteFeeType = (feeTypeId) => | ||
| deletePaymentFeeType(feeTypeId).then(() => { | ||
| getPaymentFeeTypes(currentPaymentProfile.id); | ||
| }); |
There was a problem hiding this comment.
🟡 [MEDIUM] handleDeleteFeeType and handleSaveFeeType swallow failures
.then(...) with no .catch — if the underlying request rejects, the success branch (refresh, and for save: close dialog) still chains, leaving the UI inconsistent with the server. snackbarErrorHandler will show a toast but the refresh and any state cleanup still runs.
Fix: Add an explicit .catch(() => { /* no-op */ }), and only refresh / close on the success path.
| @@ -41,13 +45,19 @@ const paymentProfileListReducer = (state = DEFAULT_STATE, action) => { | |||
| return { ...state, order, orderDir }; | |||
| } | |||
There was a problem hiding this comment.
🟡 [MEDIUM] Reducer drops term, currentPage, and perPage from REQUEST_PAYMENT_PROFILES payload
The action now dispatches { term, page, perPage, order, orderDir }, but this case only reads { order, orderDir }. As a result term is never written to redux — the term value spread by mapStateToProps stays "" and any consumer relying on it is stale. This also breaks the list-state contract documented in .claude/rules/summit-admin-redux-actions.md.
Fix:
case REQUEST_PAYMENT_PROFILES: {
const { term, currentPage, perPage, order, orderDir } = payload;
return { ...state, term, currentPage, perPage, order, orderDir };
}| component={EditPaymentFeeTypePage} | ||
| /> | ||
|
|
||
| <Route component={NoMatchPage} /> |
There was a problem hiding this comment.
🟡 [MEDIUM] Deleted routes silently 404 bookmarks and deep links
/payment-profiles/new, /payment-profiles/:id, and /payment-profiles/:id/payment-fee-type/... are removed with no redirect. Any bookmarked URL, emailed link, or in-app history.push to those paths will now hit NoMatchPage.
Fix: Either add a Redirect for legacy paths (/:id → / and optionally open the dialog via a query param), or grep the codebase for any residual history.push(".../payment-profiles/${id}") callers and update them.
| if (term) { | ||
| const escapedTerm = escapeFilterValue(term); | ||
| filter.push( | ||
| `provider=@${escapedTerm},id=@${escapedTerm},application_type=@${escapedTerm}` |
There was a problem hiding this comment.
🔵 [LOW] Search filter uses =@ (contains) on enum & id fields
provider=@x,id=@x,application_type=@x does a substring match on enum-like fields (Registration, BookableRooms, SponsorServices) and on a numeric id — typing 1 matches ids 1, 10, 11, 12…, and Sponsor matches SponsorServices even when the user meant something narrower.
Fix: Use == for id and application_type; keep =@ only on provider if free-text matching is actually intended.
| @@ -1186,6 +1187,7 @@ export const deleteRefundPolicy = | |||
|
|
|||
| export const getPaymentProfiles = | |||
There was a problem hiding this comment.
🟡 [MEDIUM] Missing test coverage for the new behavior
No __tests__ updates ship with this PR for any of the new surfaces. The following should be covered before merge:
getPaymentProfileswithterm— URL/params construction with non-emptyterm(verifyescapeFilterValue, thefilter[]array shape, and the newREQUEST_PAYMENT_PROFILESpayload).- Reducer
REQUEST_PAYMENT_PROFILES— asserts thatterm,currentPage,perPageland in state (catches the bug flagged inpayment-profile-list-reducer.js#L46). PaymentProfileDialogflow —- parent stays open after a fee-type save (catches the bug flagged in
payment-profile-list-page.js#L110), - dialog stays open if save rejects (
.catchbehaviour), - validation prevents submit when required fields are empty (once the schema is re-enabled).
- parent stays open after a fee-type save (catches the bug flagged in
This is a UX-critical surface (payment configuration). At minimum the action + reducer should be covered.
santipalenque
left a comment
There was a problem hiding this comment.
no further flags other than seba's
ref: https://app.clickup.com/t/86b9tgt19
Signed-off-by: Tomás Castillo tcastilloboireau@gmail.com
Summary by CodeRabbit
New Features
Improvements
Removed