Add new unified OrderCaptureDialog with amount options#6200
Add new unified OrderCaptureDialog with amount options#6200witoszekdev merged 102 commits intomainfrom
Conversation
… not confused when the name matches
When no products are available in the channel or channel is inactive.
- Create new OrderCaptureDialog component using macaw-ui-next - Supports three capture options: order total, authorized amount, custom - Shows contextual warnings when authorized differs from total - Integrate with both Legacy Payments API and Transactions API flows - Include development overrides for testing (to be removed)
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
witoszekdev
left a comment
There was a problem hiding this comment.
Here are my comments from other PR that had changes from this one by accident, these are still valid since the diff is the same:
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 19 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| <Input | ||
| size="small" | ||
| type="text" | ||
| inputMode="decimal" | ||
| value={customAmountInput} | ||
| onChange={handleCustomAmountChange} | ||
| error={showCustomAmountError} | ||
| disabled={ | ||
| authStatus === "none" || | ||
| authStatus === "charged" || | ||
| selectedOption !== "custom" | ||
| } | ||
| endAdornment={ | ||
| <Text size={2} color="default2" marginRight={2}> | ||
| {currency} | ||
| </Text> | ||
| } | ||
| /> |
There was a problem hiding this comment.
The custom amount input field lacks an associated label or aria-label attribute, which can impact accessibility for screen reader users. Consider adding an aria-label or associating it with a visible label element to describe its purpose.
| statusPartial: { | ||
| id: "ZUYQ+C", | ||
| defaultMessage: "Partial Authorisation", | ||
| description: "status pill for partial authorization", |
There was a problem hiding this comment.
Inconsistent spelling: The default message uses British English "authorisation" while the description uses American English "authorization". Consider using consistent spelling throughout (either "authorization" or "authorisation" for both).
| // Also check for Apollo errors (network errors, GraphQL execution errors) | ||
| // These are stored in opts.error, not in the mutation response data | ||
| const hasApolloError = !!opts.error; | ||
|
|
||
| return getMutationState( | ||
| opts.called, | ||
| opts.loading, | ||
| errors, | ||
| hasApolloError ? [{ error: opts.error }] : [], | ||
| ); |
There was a problem hiding this comment.
The error array passed to getMutationState should contain actual error objects that match the expected structure. The current implementation passes [{ error: opts.error }] which will always evaluate as truthy (non-empty array) even though it doesn't contain proper error objects. This may cause the button state to show "error" even when opts.error is undefined or null.
Consider checking if opts.error actually exists before creating the array, or pass an empty array when there's no error.
| onSubmit={amount => | ||
| orderTransactionAction | ||
| .mutate({ | ||
| action: params.type, | ||
| transactionId: params.id, | ||
| amount, | ||
| }) | ||
| .finally(() => closeModal()) | ||
| } |
There was a problem hiding this comment.
The transaction capture dialog closes immediately after submission (via .finally(() => closeModal())), which means errors won't be visible to the user. The legacy payment capture implementation (lines 448-453) doesn't have this .finally() call, allowing errors to be displayed in the dialog before it's closed. Consider removing the .finally(() => closeModal()) call to match the legacy behavior and allow users to see error messages.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 19 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| <OrderCaptureDialog | ||
| confirmButtonState={orderPaymentCapture.opts.status} | ||
| errors={orderPaymentCapture.opts.data?.orderCapture?.errors ?? []} | ||
| orderTotal={order?.total.gross ?? defaultZeroMoney} | ||
| authorizedAmount={order?.totalAuthorized ?? defaultZeroMoney} | ||
| onClose={closeModal} | ||
| onSubmit={amount => | ||
| orderPaymentCapture.mutate({ | ||
| amount, | ||
| id, | ||
| }) | ||
| } | ||
| /> |
There was a problem hiding this comment.
The OrderCaptureDialog for legacy payment capture (action === "capture") is missing a key prop. The transaction-charge-action dialog has key={params.id} at line 389, but this one doesn't. Without a key prop, React won't remount the component when switching between different orders, causing stale state in the dialog. Add a unique key prop to force remounting when the order changes.
| <OrderCaptureDialog | ||
| confirmButtonState={orderPaymentCapture.opts.status} | ||
| errors={orderPaymentCapture.opts.data?.orderCapture?.errors ?? []} | ||
| orderTotal={order.total.gross} | ||
| authorizedAmount={order.totalAuthorized} | ||
| onClose={closeModal} | ||
| onSubmit={amount => | ||
| orderPaymentCapture.mutate({ | ||
| amount, | ||
| id, | ||
| }) | ||
| } | ||
| /> |
There was a problem hiding this comment.
The OrderCaptureDialog for legacy payment capture (action === "capture") is missing a key prop. The transaction-charge-action dialog has key={params.id} at line 323, but this one doesn't. Without a key prop, React won't remount the component when switching between different orders, causing stale state in the dialog. Add a unique key prop to force remounting when the order changes.
| export const limitDecimalPlaces = (value: string, maxDecimalPlaces: number): string => { | ||
| const normalized = normalizeDecimalSeparator(value); | ||
| const separator = value.includes(",") ? "," : "."; | ||
| const [integerPart, decimalPart] = normalized.split("."); | ||
|
|
||
| if (!decimalPart) { | ||
| return value; | ||
| } | ||
|
|
||
| if (maxDecimalPlaces === 0) { | ||
| return integerPart; | ||
| } | ||
|
|
||
| if (decimalPart.length > maxDecimalPlaces) { | ||
| return `${integerPart}${separator}${decimalPart.slice(0, maxDecimalPlaces)}`; |
There was a problem hiding this comment.
When the user types a value with thousand separators (e.g., "1,234.56"), the function will detect the separator as "," at line 90 because the value contains a comma, even though the decimal separator is actually a dot. This will cause the limited value to be reconstructed with the wrong separator (e.g., if limiting to 1 decimal, it might become "1234,5" instead of "1234.5"). Consider detecting the decimal separator by checking which separator appears last in the original value, or by checking the normalized value's decimal position.
| jest.mock("@saleor/macaw-ui-next", () => ({ | ||
| ...(jest.requireActual("@saleor/macaw-ui-next") as object), | ||
| useTheme: () => ({ theme: "default" }), | ||
| })); |
There was a problem hiding this comment.
The mock for useTheme returns { theme: "default" }, but the actual useTheme hook from macaw-ui-next likely returns a theme value of type DefaultTheme which should be either "defaultLight" or "defaultDark", not "default". This could cause tests to fail or produce incorrect results when testing theme-dependent functionality. Verify the correct theme value by checking the DefaultTheme type definition or update to use "defaultLight" which is the typical default.



Summary
OrderCaptureDialogfor both Legacy Payments and Transactions APIsPriceField/utils.ts:normalizeDecimalSeparator,parseDecimalValue,limitDecimalPlacesCalloutcomponent dark mode colors using theme-awaregetStatusColorTest plan
totalAuthorized > 0authorizedAmount > 0