fix: migrate payment profiles to MUI components#925
Conversation
Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughConsolidates payment profile and fee-type management into a single list page with a modal dialog, adds search/pagination to Redux, standardizes snackbar-based API notifications, removes legacy edit pages, simplifies routing, and adds tests, i18n updates, and constants. ChangesPayment Profile Dialog-Based Management
Sequence DiagramsequenceDiagram
participant User
participant ListPage as Payment Profile<br/>List Page
participant Dialog
participant Redux
participant API as Purchases API
participant Snack as Snackbar
User->>ListPage: load/search/paginate/click edit
ListPage->>Redux: dispatch getPaymentProfiles(term,page,perPage,order,orderDir)
Redux->>API: GET /summits/{id}/payment-profiles?filter[]=...&page=&per_page=
API-->>Redux: response with data and pagination metadata
Redux-->>ListPage: REQUEST + RECEIVE actions with list and params
ListPage->>User: render table with profiles
User->>ListPage: click edit or add payment profile
ListPage->>Redux: dispatch getPaymentProfile(id) or getPaymentFeeTypes(profile_id)
Redux->>API: fetch entity details
API-->>Redux: return entity
Redux-->>ListPage: update currentPaymentProfile or paymentFeeTypes
ListPage->>Dialog: open(entity, paymentFeeTypes, isSaving)
Dialog->>User: show profile and fee-type forms
User->>Dialog: submit profile form
Dialog->>Redux: dispatch savePaymentProfile(entity)
Redux->>API: POST/PUT /summits/{id}/payment-profiles
alt success
API-->>Redux: success response
Redux->>Snack: snackbarSuccessHandler
Redux-->>ListPage: RECEIVE and close dialog
else error
API-->>Redux: error response
Redux->>Snack: snackbarErrorHandler
end
Snack->>User: show notification
User->>Dialog: manage fee types (add/edit/delete)
Dialog->>Redux: dispatch savePaymentFeeType or deletePaymentFeeType
Redux->>API: save/delete fee type
API-->>Redux: success or error
Redux->>Snack: snackbar notification
Redux-->>Dialog: reload paymentFeeTypes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~65 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 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
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.
santipalenque
left a comment
There was a problem hiding this comment.
no further flags other than seba's
Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>
There was a problem hiding this comment.
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 (1)
src/pages/tickets/payment-profile/components/payment-profile-dialog.js (1)
217-223:⚠️ Potential issue | 🟠 Major | ⚡ Quick win
max_cents/min_centsare edited as price but displayed as raw cents in the table.You converted form inputs to price fields (
inCents), but the table still renders raw stored integers formax_centsandmin_cents. This leaves a unit mismatch in the UI.Suggested fix
{ columnKey: "max_cents", - header: T.translate("edit_payment_profile.payment_type_fee_max_cents") + header: T.translate("edit_payment_profile.payment_type_fee_max_cents"), + render: (row) => currencyAmountFromCents(row.max_cents || 0) }, { columnKey: "min_cents", - header: T.translate("edit_payment_profile.payment_type_fee_min_cents") + header: T.translate("edit_payment_profile.payment_type_fee_min_cents"), + render: (row) => currencyAmountFromCents(row.min_cents || 0) }Also applies to: 584-604
🤖 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 217 - 223, Table columns for "max_cents" and "min_cents" show raw integer cents while the form and inputs use price units; update the table column renderers for columnKey "max_cents" and "min_cents" to convert cents to a human price string (e.g., divide by 100 and format with the app's currency formatter or existing helper used elsewhere—look for any formatPrice/formatCurrency or inCents utilities) so the table displays the same price units as the form; ensure the conversion is applied both where these columns are defined and in the other occurrence noted (lines ~584-604) so all table displays match the edited price inputs.
🧹 Nitpick comments (1)
src/pages/tickets/payment-profile/components/__tests__/payment-profile-dialog.test.js (1)
318-325: ⚡ Quick winExtend the formatting test to assert
max_cents/min_centsdisplay conversion too.This test already guards Rate/Amount
valueformatting; adding max/min assertions will catch cents-vs-USD regressions in the same path.Suggested test extension
const amountFeeType = { id: 2, name: "Flat Fee", kind: "Amount", payment_method: "card", value: 1500, // $15.00 — stored in cents - max_cents: 0, - min_cents: 0 + max_cents: 2500, // $25.00 + min_cents: 500 // $5.00 }; @@ test("formats Rate value as a percentage and Amount value as money", () => { renderDialog({ entity: feeTypeSectionEntity, paymentFeeTypes }); // rateFeeType row 0: value = 250 → 250/100 = 2.5% expect(screen.getByTestId("cell-value-0")).toHaveTextContent("2.5%"); // amountFeeType row 1: value = 1500 cents → $15.00 expect(screen.getByTestId("cell-value-1")).toHaveTextContent("$15.00"); + expect(screen.getByTestId("cell-max_cents-1")).toHaveTextContent("$25.00"); + expect(screen.getByTestId("cell-min_cents-1")).toHaveTextContent("$5.00"); });🤖 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/__tests__/payment-profile-dialog.test.js` around lines 318 - 325, Update the "formats Rate value as a percentage and Amount value as money" test (the test block that calls renderDialog with feeTypeSectionEntity and paymentFeeTypes) to also assert the formatted display for the min_cents and max_cents cells: add expectations using screen.getByTestId for "cell-min_cents-0"/"cell-max_cents-0" (rateFeeType row 0) and "cell-min_cents-1"/"cell-max_cents-1" (amountFeeType row 1); for the Rate row assert min/max are shown as percentages (value/100 with a "%" suffix) and for the Amount row assert min/max are shown as USD money strings (cents → dollars with two decimals, e.g. 1500 → "$15.00"). Ensure these new assertions follow the existing pattern in the test that checks "cell-value-0" and "cell-value-1".
🤖 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 163-170: The max_cents/min_cents validation is capping cent values
at 99 which conflicts with MuiFormikPriceField's inCents usage; update the
decimalValidation() calls for max_cents and min_cents to either remove the
.max(...) cap or replace MAX_CENTS with the actual backend-aligned cents limit
(e.g., BACKEND_MAX_CENTS), and adjust the T.translate message to show the
correct unit (use cents or convert MAX_CENTS to dollars if the translation
expects dollars); locate the validation block containing max_cents, min_cents,
decimalValidation(), and MAX_CENTS and modify it accordingly so cent inputs are
not incorrectly blocked.
- Around line 172-177: The onSubmit handler currently calls
onSaveFeeType(values).then(...) but doesn’t return the resulting promise,
preventing Formik from tracking async submission; update the onSubmit in the fee
type Formik config to return the promise from onSaveFeeType(values).then(...),
keeping the existing then callback (feeTypeFormik.resetForm();
setShowFeeTypeForm(false)); this ensures Formik’s isSubmitting lifecycle is
properly managed when onSaveFeeType/handleSaveFeeType/savePaymentFeeType resolve
or reject.
---
Outside diff comments:
In `@src/pages/tickets/payment-profile/components/payment-profile-dialog.js`:
- Around line 217-223: Table columns for "max_cents" and "min_cents" show raw
integer cents while the form and inputs use price units; update the table column
renderers for columnKey "max_cents" and "min_cents" to convert cents to a human
price string (e.g., divide by 100 and format with the app's currency formatter
or existing helper used elsewhere—look for any formatPrice/formatCurrency or
inCents utilities) so the table displays the same price units as the form;
ensure the conversion is applied both where these columns are defined and in the
other occurrence noted (lines ~584-604) so all table displays match the edited
price inputs.
---
Nitpick comments:
In
`@src/pages/tickets/payment-profile/components/__tests__/payment-profile-dialog.test.js`:
- Around line 318-325: Update the "formats Rate value as a percentage and Amount
value as money" test (the test block that calls renderDialog with
feeTypeSectionEntity and paymentFeeTypes) to also assert the formatted display
for the min_cents and max_cents cells: add expectations using screen.getByTestId
for "cell-min_cents-0"/"cell-max_cents-0" (rateFeeType row 0) and
"cell-min_cents-1"/"cell-max_cents-1" (amountFeeType row 1); for the Rate row
assert min/max are shown as percentages (value/100 with a "%" suffix) and for
the Amount row assert min/max are shown as USD money strings (cents → dollars
with two decimals, e.g. 1500 → "$15.00"). Ensure these new assertions follow the
existing pattern in the test that checks "cell-value-0" and "cell-value-1".
🪄 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: b38ccc6b-74da-4c4e-9e38-7559d82e59ad
📒 Files selected for processing (8)
src/actions/__tests__/payment-profile-actions.test.jssrc/pages/tickets/payment-profile/__tests__/payment-profile-list-page.test.jssrc/pages/tickets/payment-profile/components/__tests__/payment-profile-dialog.test.jssrc/pages/tickets/payment-profile/components/payment-profile-dialog.jssrc/pages/tickets/payment-profile/payment-profile-list-page.jssrc/reducers/payment_profiles/__tests__/payment-profile-list-reducer.test.jssrc/reducers/payment_profiles/payment-profile-list-reducer.jssrc/utils/constants.js
✅ Files skipped from review due to trivial changes (1)
- src/utils/constants.js
🚧 Files skipped from review as they are similar to previous changes (2)
- src/reducers/payment_profiles/payment-profile-list-reducer.js
- src/pages/tickets/payment-profile/payment-profile-list-page.js
Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>
Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>
Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>
…nsability Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>
Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>
…rm filter Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>
Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>
…and promise-chain tests - savePaymentProfile POST path: verifies START_LOADING, UPDATE_PAYMENT_PROFILE, PAYMENT_PROFILE_ADDED, SET_SNACKBAR_MESSAGE, STOP_LOADING sequence, correct summit URL, and entity passed as request body - savePaymentProfile PUT path: same sequence with PAYMENT_PROFILE_UPDATED, URL includes entity id, and STOP_LOADING fires via .finally even on request failure - deletePaymentFeeType: verifies PAYMENT_FEE_TYPE_DELETED payload and correct URL; explicitly documents that START_LOADING is never dispatched (pre-existing gap) - list-page onSave chain: verifies that when savePaymentProfile succeeds but the subsequent getPaymentProfiles call rejects, onSave rejects and the dialog stays mounted, preventing a silent duplicate creation on retry
There was a problem hiding this comment.
Pull request overview
Migrates the “Payment Profiles” area in the tickets section from legacy pages/forms to a consolidated MUI-based list page with a create/edit dialog, while updating related Redux state and ticket actions to support searching/pagination and snackbar-style notifications.
Changes:
- Added a new payment profile list page (MUI table + search + pagination) and a MUI dialog for creating/editing profiles and managing fee types.
- Updated payment profile list reducer and ticket actions to support search term + pagination metadata and to use snackbar handlers.
- Removed legacy standalone routes/pages and old form components; updated layout routing and i18n strings; added unit tests.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/utils/constants.js | Adds numeric constants used by new validation logic. |
| src/reducers/payment_profiles/payment-profile-list-reducer.js | Extends reducer state to store term and pagination metadata. |
| src/reducers/payment_profiles/tests/payment-profile-list-reducer.test.js | Adds unit coverage for new reducer behavior (term + pagination). |
| src/pages/tickets/payment-profile/payment-profile-list-page.js | New MUI list page with search/sort/pagination and dialog flow. |
| src/pages/tickets/payment-profile/components/payment-profile-dialog.js | New MUI dialog with Formik/yup validation and fee-type management UI. |
| src/pages/tickets/payment-profile/components/tests/payment-profile-dialog.test.js | Adds dialog interaction/validation/value-formatting unit tests. |
| src/pages/tickets/payment-profile/tests/payment-profile-list-page.test.js | Adds list-page tests around dialog open/close and save flows. |
| src/pages/tickets/payment-profile-list-page.js | Removes legacy payment profile list page implementation. |
| src/pages/tickets/edit-payment-profile-page.js | Removes legacy edit payment profile page. |
| src/pages/tickets/edit-payment-fee-type-page.js | Removes legacy edit payment fee type page. |
| src/layouts/payment-profile-layout.js | Updates routing to point to the new consolidated list page. |
| src/i18n/en.json | Updates copy (delete confirmation, search placeholder, fee labels). |
| src/components/forms/payment-profile-form.js | Removes legacy form component replaced by MUI dialog. |
| src/components/forms/payment-fee-type-form.js | Removes legacy fee-type form component replaced by dialog form. |
| src/actions/ticket-actions.js | Adds search filtering for payment profiles and switches payment operations to snackbar handlers. |
| src/actions/tests/payment-profile-actions.test.js | Adds tests validating filter construction and request payload details. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…tion - Add isSaving guard to handleCancelFeeType so cancelling during an in-flight save does not hide the form while the main save button stays stuck disabled - Disable 'Save Fee Type' button while isSaving to prevent double-submit - Add deleteDialogBody to fee type MuiTable so deleting a fee type requires explicit confirmation, consistent with the profile list - Add i18n key edit_payment_profile.fee_type_remove_warning - Fix pre-existing ESLint warnings: InfoTooltip PropTypes, camelCase variable names, PropTypes.object/array replaced with PropTypes.shape
ref: https://app.clickup.com/t/86b9tgt19
Signed-off-by: Tomás Castillo tcastilloboireau@gmail.com
Summary by CodeRabbit
New Features
Improvements
Removed
Tests