Skip to content

Commit 1dba5c2

Browse files
authored
refactor(checkout): CHECKOUT-9386 Convert ManageInstrumentsModal (#2554)
* refactor(checkout): CHECKOUT-9386 Refactor ManageInstrumentsModal * Fix test * Update useCallback
1 parent 5b07e56 commit 1dba5c2

File tree

2 files changed

+99
-185
lines changed

2 files changed

+99
-185
lines changed

packages/instrument-utils/src/storedInstrument/ManageInstrumentsModal/ManageInstrumentsModal.test.tsx

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -55,16 +55,6 @@ describe('ManageInstrumentsModal', () => {
5555
);
5656
});
5757

58-
it('throws an error if not wrapped in checkout context', () => {
59-
expect(() =>
60-
render(
61-
<LocaleContext.Provider value={localeContext}>
62-
<ManageInstrumentsModal {...defaultProps} />
63-
</LocaleContext.Provider>,
64-
),
65-
).toThrow('Need to wrap in checkout context');
66-
});
67-
6858
it('renders list of card instruments in table format', () => {
6959
render(
7060
<ManageInstrumentsModalTest
Lines changed: 99 additions & 175 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { type PaymentInstrument } from '@bigcommerce/checkout-sdk';
22
import { noop } from 'lodash';
3-
import React, { Component, type ReactNode } from 'react';
3+
import React, { type ReactElement, useState } from 'react';
44

55
import { TranslatedString } from '@bigcommerce/checkout/locale';
6-
import { CheckoutContext } from '@bigcommerce/checkout/payment-integration-api';
6+
import { useCheckout } from '@bigcommerce/checkout/payment-integration-api';
77
import { Button, ButtonSize, ButtonVariant, Modal, ModalHeader } from '@bigcommerce/checkout/ui';
88

99
import {
@@ -26,71 +26,64 @@ export interface ManageInstrumentsModalProps {
2626
onRequestClose?(): void;
2727
}
2828

29-
export interface ManageInstrumentsModalState {
30-
isConfirmingDelete: boolean;
31-
selectedInstrumentId?: string;
32-
}
33-
34-
class ManageInstrumentsModal extends Component<
35-
ManageInstrumentsModalProps,
36-
ManageInstrumentsModalState
37-
> {
38-
static contextType = CheckoutContext;
39-
declare context: React.ContextType<typeof CheckoutContext>;
40-
41-
state: ManageInstrumentsModalState = {
42-
isConfirmingDelete: false,
29+
const ManageInstrumentsModal = ({
30+
isOpen,
31+
instruments,
32+
onAfterOpen,
33+
onDeleteInstrument = noop,
34+
onDeleteInstrumentError = noop,
35+
onRequestClose,
36+
}: ManageInstrumentsModalProps): ReactElement => {
37+
const [isConfirmingDelete, setIsConfirmingDelete] = useState(false);
38+
const [selectedInstrumentId, setSelectedInstrumentId] = useState<string | undefined>();
39+
40+
const {
41+
checkoutState: {
42+
errors: { getDeleteInstrumentError },
43+
statuses: { isDeletingInstrument, isLoadingInstruments },
44+
},
45+
checkoutService: { deleteInstrument, clearError },
46+
} = useCheckout();
47+
48+
const deleteInstrumentError = getDeleteInstrumentError();
49+
50+
const handleAfterOpen = (): void => {
51+
setIsConfirmingDelete(false);
52+
onAfterOpen?.();
4353
};
4454

45-
render(): ReactNode {
46-
if (!this.context) {
47-
throw Error('Need to wrap in checkout context');
48-
}
49-
50-
const {
51-
checkoutState: {
52-
errors: { getDeleteInstrumentError },
53-
},
54-
} = this.context;
55+
const handleCancel = (): void => {
56+
const existingError = getDeleteInstrumentError();
5557

56-
const deleteInstrumentError = getDeleteInstrumentError();
58+
if (existingError) {
59+
void clearError(existingError);
60+
}
5761

58-
const { isOpen, onRequestClose } = this.props;
62+
setIsConfirmingDelete(false);
63+
};
5964

60-
return (
61-
<Modal
62-
closeButtonLabel={<TranslatedString id="common.close_action" />}
63-
footer={this.renderFooter()}
64-
header={
65-
<ModalHeader>
66-
<TranslatedString id="payment.instrument_manage_modal_title_text" />
67-
</ModalHeader>
68-
}
69-
isOpen={isOpen}
70-
onAfterOpen={this.handleAfterOpen}
71-
onRequestClose={onRequestClose}
72-
>
73-
{deleteInstrumentError && <ManageInstrumentsAlert error={deleteInstrumentError} />}
65+
const handleConfirmDelete = async (): Promise<void> => {
66+
if (!selectedInstrumentId) {
67+
return;
68+
}
7469

75-
{this.renderContent()}
76-
</Modal>
77-
);
78-
}
70+
try {
71+
await deleteInstrument(selectedInstrumentId);
72+
onDeleteInstrument(selectedInstrumentId);
73+
onRequestClose?.();
74+
} catch (error) {
75+
const err = error instanceof Error ? error : new Error(String(error));
7976

80-
private renderContent(): ReactNode {
81-
if (!this.context) {
82-
throw Error('Need to wrap in checkout context');
77+
onDeleteInstrumentError(err);
8378
}
79+
};
8480

85-
const {
86-
checkoutState: {
87-
statuses: { isDeletingInstrument },
88-
},
89-
} = this.context;
90-
const { instruments } = this.props;
91-
92-
const { isConfirmingDelete } = this.state;
81+
const handleDeleteInstrument = (id: string): void => {
82+
setIsConfirmingDelete(true);
83+
setSelectedInstrumentId(id);
84+
};
9385

86+
const ModalContent = () => {
9487
if (isConfirmingDelete) {
9588
return (
9689
<p>
@@ -109,7 +102,7 @@ class ManageInstrumentsModal extends Component<
109102
<ManageAchInstrumentsTable
110103
instruments={achInstrument}
111104
isDeletingInstrument={isDeletingInstrument()}
112-
onDeleteInstrument={this.handleDeleteInstrument}
105+
onDeleteInstrument={handleDeleteInstrument}
113106
/>
114107
);
115108
}
@@ -121,7 +114,7 @@ class ManageInstrumentsModal extends Component<
121114
<ManageAccountInstrumentsTable
122115
instruments={bankAndAccountInstruments}
123116
isDeletingInstrument={isDeletingInstrument()}
124-
onDeleteInstrument={this.handleDeleteInstrument}
117+
onDeleteInstrument={handleDeleteInstrument}
125118
/>
126119
);
127120
}
@@ -130,129 +123,60 @@ class ManageInstrumentsModal extends Component<
130123
<ManageCardInstrumentsTable
131124
instruments={cardInstruments}
132125
isDeletingInstrument={isDeletingInstrument()}
133-
onDeleteInstrument={this.handleDeleteInstrument}
126+
onDeleteInstrument={handleDeleteInstrument}
134127
/>
135128
);
136-
}
137-
138-
private renderFooter(): ReactNode {
139-
if (!this.context) {
140-
throw Error('Need to wrap in checkout context');
141-
}
142-
143-
const {
144-
checkoutState: {
145-
statuses: { isDeletingInstrument, isLoadingInstruments },
146-
},
147-
} = this.context;
148-
149-
const { onRequestClose } = this.props;
150-
const { isConfirmingDelete } = this.state;
151-
152-
if (isConfirmingDelete) {
153-
return (
154-
<>
155-
<Button
156-
onClick={this.handleCancel}
157-
size={ButtonSize.Small}
158-
testId="manage-instrument-cancel-button"
159-
>
160-
<TranslatedString id="common.cancel_action" />
161-
</Button>
162-
163-
<Button
164-
disabled={isDeletingInstrument() || isLoadingInstruments()}
165-
onClick={this.handleConfirmDelete}
166-
size={ButtonSize.Small}
167-
testId="manage-instrument-confirm-button"
168-
variant={ButtonVariant.Primary}
169-
>
170-
<TranslatedString id="payment.instrument_manage_modal_confirmation_action" />
171-
</Button>
172-
</>
173-
);
174-
}
129+
};
175130

176-
return (
131+
const ConfirmDelete = () => (
132+
<>
177133
<Button
178-
onClick={onRequestClose}
134+
onClick={handleCancel}
179135
size={ButtonSize.Small}
180-
testId="manage-instrument-close-button"
136+
testId="manage-instrument-cancel-button"
181137
>
182-
<TranslatedString id="common.close_action" />
138+
<TranslatedString id="common.cancel_action" />
183139
</Button>
184-
);
185-
}
186140

187-
private handleAfterOpen: () => void = () => {
188-
const { onAfterOpen } = this.props;
189-
190-
this.setState(
191-
{
192-
isConfirmingDelete: false,
193-
},
194-
onAfterOpen,
195-
);
196-
};
197-
198-
private handleCancel: () => void = () => {
199-
if (!this.context) {
200-
throw Error('Need to wrap in checkout context');
201-
}
202-
203-
const {
204-
checkoutState: {
205-
errors: { getDeleteInstrumentError },
206-
},
207-
checkoutService: { clearError },
208-
} = this.context;
209-
210-
const deleteInstrumentError = getDeleteInstrumentError();
211-
212-
if (deleteInstrumentError) {
213-
void clearError(deleteInstrumentError);
214-
}
215-
216-
this.setState({
217-
isConfirmingDelete: false,
218-
});
219-
};
220-
221-
private handleConfirmDelete: () => void = async () => {
222-
if (!this.context) {
223-
throw Error('Need to wrap in checkout context');
224-
}
225-
226-
const {
227-
checkoutService: { deleteInstrument },
228-
} = this.context;
229-
230-
const {
231-
onDeleteInstrument = noop,
232-
onDeleteInstrumentError = noop,
233-
onRequestClose = noop,
234-
} = this.props;
235-
const { selectedInstrumentId } = this.state;
236-
237-
if (!selectedInstrumentId) {
238-
return;
239-
}
240-
241-
try {
242-
await deleteInstrument(selectedInstrumentId);
243-
onDeleteInstrument(selectedInstrumentId);
244-
onRequestClose();
245-
} catch (error) {
246-
onDeleteInstrumentError(error);
247-
}
248-
};
249-
250-
private handleDeleteInstrument: (id: string) => void = (id) => {
251-
this.setState({
252-
isConfirmingDelete: true,
253-
selectedInstrumentId: id,
254-
});
255-
};
256-
}
141+
<Button
142+
disabled={isDeletingInstrument() || isLoadingInstruments()}
143+
onClick={handleConfirmDelete}
144+
size={ButtonSize.Small}
145+
testId="manage-instrument-confirm-button"
146+
variant={ButtonVariant.Primary}
147+
>
148+
<TranslatedString id="payment.instrument_manage_modal_confirmation_action" />
149+
</Button>
150+
</>
151+
);
152+
const CloseButton = () => (
153+
<Button
154+
onClick={onRequestClose}
155+
size={ButtonSize.Small}
156+
testId="manage-instrument-close-button"
157+
>
158+
<TranslatedString id="common.close_action" />
159+
</Button>
160+
);
161+
162+
return (
163+
<Modal
164+
closeButtonLabel={<TranslatedString id="common.close_action" />}
165+
footer={isConfirmingDelete ? <ConfirmDelete /> : <CloseButton />}
166+
header={
167+
<ModalHeader>
168+
<TranslatedString id="payment.instrument_manage_modal_title_text" />
169+
</ModalHeader>
170+
}
171+
isOpen={isOpen}
172+
onAfterOpen={handleAfterOpen}
173+
onRequestClose={onRequestClose}
174+
>
175+
{deleteInstrumentError && <ManageInstrumentsAlert error={deleteInstrumentError} />}
176+
177+
<ModalContent />
178+
</Modal>
179+
);
180+
};
257181

258182
export default ManageInstrumentsModal;

0 commit comments

Comments
 (0)