@akinon/pz-saved-card 1.67.0 → 1.68.0
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +6 -9
- package/assets/img/amex.jpg +0 -0
- package/assets/img/mastercard.png +0 -0
- package/assets/img/other.png +0 -0
- package/assets/img/troy.png +0 -0
- package/assets/img/visa.png +0 -0
- package/package.json +6 -4
- package/readme.md +36 -27
- package/src/components/agreement-and-submit.tsx +25 -0
- package/src/components/card-label.tsx +17 -0
- package/src/components/card-selection-section.tsx +51 -0
- package/src/components/delete-confirmation-modal.tsx +80 -0
- package/src/components/delete-icon.tsx +8 -0
- package/src/components/error-text.tsx +7 -0
- package/src/components/installment-section.tsx +22 -0
- package/src/components/installments.tsx +113 -0
- package/src/index.tsx +2 -4
- package/src/{endpoints.ts → redux/api.ts} +65 -23
- package/src/redux/reducer.ts +25 -13
- package/src/types/index.ts +50 -16
- package/src/utils/index.ts +15 -0
- package/src/views/saved-card-option.tsx +232 -0
- package/src/redux/middleware.ts +0 -25
- package/src/routes/saved-card-redirect.tsx +0 -25
- package/src/views/installments.tsx +0 -108
- package/src/views/option.tsx +0 -400
package/CHANGELOG.md
CHANGED
@@ -1,13 +1,10 @@
|
|
1
1
|
# @akinon/pz-saved-card
|
2
2
|
|
3
|
-
## 1.
|
3
|
+
## 1.68.0
|
4
4
|
|
5
|
-
|
5
|
+
### Minor Changes
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
## 1.63.0
|
12
|
-
|
13
|
-
## 1.62.0
|
7
|
+
- b92001c: ZERO-2903: fix missing dependency in useEffect hook
|
8
|
+
- 63597bc: ZERO-2903: update saved card readme
|
9
|
+
- 8fb37c4: ZERO-2903: enchance SavedCardOption component with custom wrapper props and classnames
|
10
|
+
- ce25dac: ZERO-2903: optimize saved card
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
package/package.json
CHANGED
@@ -1,20 +1,22 @@
|
|
1
1
|
{
|
2
2
|
"name": "@akinon/pz-saved-card",
|
3
|
-
"version": "1.
|
3
|
+
"version": "1.68.0",
|
4
4
|
"license": "MIT",
|
5
5
|
"main": "src/index.tsx",
|
6
6
|
"peerDependencies": {
|
7
7
|
"react": "^18.0.0",
|
8
8
|
"react-dom": "^18.0.0"
|
9
9
|
},
|
10
|
+
"dependencies": {
|
11
|
+
"react-redux": "8.1.3"
|
12
|
+
},
|
10
13
|
"devDependencies": {
|
11
14
|
"@types/node": "^18.7.8",
|
12
15
|
"@types/react": "^18.0.17",
|
13
16
|
"@types/react-dom": "^18.0.6",
|
17
|
+
"prettier": "^3.0.3",
|
14
18
|
"react": "^18.2.0",
|
15
19
|
"react-dom": "^18.2.0",
|
16
|
-
"typescript": "^
|
17
|
-
"react-hook-form": "7.31.3",
|
18
|
-
"@hookform/resolvers": "2.9.0"
|
20
|
+
"typescript": "^5.2.2"
|
19
21
|
}
|
20
22
|
}
|
package/readme.md
CHANGED
@@ -1,36 +1,45 @@
|
|
1
|
-
#
|
1
|
+
# Saved Card Plugin
|
2
2
|
|
3
|
-
|
4
|
-
##### File Path: src/views/checkout/steps/payment/options/saved-card.tsx
|
3
|
+
## Installation
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
export default function SavedCard() {
|
11
|
-
return (
|
12
|
-
<PluginModule
|
13
|
-
component={Component.SavedCard}
|
14
|
-
props={{
|
15
|
-
agreementCheckbox: (
|
16
|
-
<CheckoutAgreements control={null} fieldId="agreement" error={null} />
|
17
|
-
)
|
18
|
-
}}
|
19
|
-
/>
|
20
|
-
);
|
21
|
-
}
|
5
|
+
There are two ways to install the Saved Card plugin:
|
6
|
+
|
7
|
+
### 1. Install the npm package using Yarn
|
22
8
|
|
9
|
+
For the latest version, you can install the package using Yarn:
|
10
|
+
|
11
|
+
```bash
|
12
|
+
yarn add @akinon/pz-saved-card
|
23
13
|
```
|
24
14
|
|
25
|
-
|
15
|
+
### 2. Preferred installation method
|
26
16
|
|
27
|
-
|
28
|
-
import { SavedCardRedirect } from 'pz-saved-card/src/routes/saved-card-redirect';
|
17
|
+
You can also use the following command to install the extension with the latest plugins:
|
29
18
|
|
30
|
-
|
19
|
+
```bash
|
20
|
+
npx @akinon/projectzero@latest --plugins
|
31
21
|
```
|
32
22
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
##### File Path: src/views/checkout/steps/payment/options/saved-card.tsx
|
26
|
+
|
27
|
+
```jsx
|
28
|
+
import PluginModule, { Component } from '@akinon/next/components/plugin-module';
|
29
|
+
|
30
|
+
const SavedCard = () => {
|
31
|
+
return (
|
32
|
+
<PluginModule
|
33
|
+
component={Component.SavedCard}
|
34
|
+
props={{
|
35
|
+
texts: {
|
36
|
+
title: 'Pay with Saved Card',
|
37
|
+
button: 'Pay Now'
|
38
|
+
}
|
39
|
+
}}
|
40
|
+
/>
|
41
|
+
);
|
42
|
+
};
|
43
|
+
|
44
|
+
export default SavedCard;
|
45
|
+
```
|
@@ -0,0 +1,25 @@
|
|
1
|
+
import React, { cloneElement } from 'react';
|
2
|
+
import { Button, Icon } from '@akinon/next/components';
|
3
|
+
|
4
|
+
export const AgreementAndSubmit = ({
|
5
|
+
agreementCheckbox,
|
6
|
+
control,
|
7
|
+
errors,
|
8
|
+
buttonText
|
9
|
+
}) => (
|
10
|
+
<div className="flex flex-col text-xs pb-4 px-4 sm:px-6">
|
11
|
+
{agreementCheckbox &&
|
12
|
+
cloneElement(agreementCheckbox, {
|
13
|
+
control,
|
14
|
+
error: errors.agreement,
|
15
|
+
fieldId: 'agreement'
|
16
|
+
})}
|
17
|
+
<Button
|
18
|
+
type="submit"
|
19
|
+
className="group uppercase mt-4 inline-flex items-center justify-center"
|
20
|
+
>
|
21
|
+
<span>{buttonText}</span>
|
22
|
+
<Icon name="chevron-end" size={12} className="ml-2 h-3" />
|
23
|
+
</Button>
|
24
|
+
</div>
|
25
|
+
);
|
@@ -0,0 +1,17 @@
|
|
1
|
+
import { getCreditCardType } from '../utils';
|
2
|
+
import React from 'react';
|
3
|
+
import { cardImages } from '../views/saved-card-option';
|
4
|
+
import { Image } from '@akinon/next/components';
|
5
|
+
|
6
|
+
export const CardLabel = ({ card }) => (
|
7
|
+
<label className="flex flex-col w-full cursor-pointer md:flex-row md:items-center md:justify-between">
|
8
|
+
<p className="w-full text-[10px] lg:w-1/3">{card.masked_card_number}</p>
|
9
|
+
<Image
|
10
|
+
className="w-8 h-6 object-contain flex items-center justify-center"
|
11
|
+
width={50}
|
12
|
+
height={50}
|
13
|
+
src={cardImages[getCreditCardType(card.masked_card_number)].src}
|
14
|
+
alt={card.name}
|
15
|
+
/>
|
16
|
+
</label>
|
17
|
+
);
|
@@ -0,0 +1,51 @@
|
|
1
|
+
import { setDeletionModalId, setDeletionModalVisible } from '../redux/reducer';
|
2
|
+
import React from 'react';
|
3
|
+
import { ErrorText } from './error-text';
|
4
|
+
import { CardLabel } from './card-label';
|
5
|
+
import { DeleteIcon } from './delete-icon';
|
6
|
+
|
7
|
+
export const CardSelectionSection = ({
|
8
|
+
title,
|
9
|
+
cards,
|
10
|
+
selectedCard,
|
11
|
+
onSelect,
|
12
|
+
register,
|
13
|
+
errors,
|
14
|
+
dispatch
|
15
|
+
}) => (
|
16
|
+
<div className="border-solid border-gray-400 px-4 py-2">
|
17
|
+
<span className="text-black-800 text-lg font-medium">{title}</span>
|
18
|
+
<ul className="mt-4 text-xs w-full">
|
19
|
+
{cards?.map((card) => (
|
20
|
+
<li
|
21
|
+
key={card.token}
|
22
|
+
className="p-4 mb-2 border-2 border-gray-200 flex justify-between items-center cursor-pointer"
|
23
|
+
onClick={(e) => {
|
24
|
+
e.preventDefault();
|
25
|
+
onSelect(card);
|
26
|
+
}}
|
27
|
+
>
|
28
|
+
<input
|
29
|
+
name="card"
|
30
|
+
type="radio"
|
31
|
+
checked={selectedCard?.token === card.token}
|
32
|
+
value={card.token}
|
33
|
+
id={card.token}
|
34
|
+
className="mr-2"
|
35
|
+
onChange={() => {}}
|
36
|
+
{...register('card')}
|
37
|
+
/>
|
38
|
+
<CardLabel card={card} />
|
39
|
+
<DeleteIcon
|
40
|
+
onClick={(e) => {
|
41
|
+
e.stopPropagation();
|
42
|
+
dispatch(setDeletionModalId(card.id));
|
43
|
+
dispatch(setDeletionModalVisible(true));
|
44
|
+
}}
|
45
|
+
/>
|
46
|
+
</li>
|
47
|
+
))}
|
48
|
+
</ul>
|
49
|
+
{errors.card && <ErrorText message={errors.card?.message} />}
|
50
|
+
</div>
|
51
|
+
);
|
@@ -0,0 +1,80 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import { Button, LoaderSpinner, Modal } from '@akinon/next/components';
|
4
|
+
import { useAppDispatch, useAppSelector } from '@akinon/next/redux/hooks';
|
5
|
+
import {
|
6
|
+
setCards,
|
7
|
+
setDeletionModalId,
|
8
|
+
setDeletionModalVisible
|
9
|
+
} from '../redux/reducer';
|
10
|
+
import { useDeleteSavedCardMutation } from '../redux/api';
|
11
|
+
import { useState } from 'react';
|
12
|
+
import { DeletePopupTexts } from '../types';
|
13
|
+
|
14
|
+
const defaultTranslations = {
|
15
|
+
title: 'Are you sure you want to delete this card?',
|
16
|
+
delete_button: 'Delete',
|
17
|
+
cancel_button: 'Cancel'
|
18
|
+
};
|
19
|
+
|
20
|
+
export interface DeleteConfirmationModalProps {
|
21
|
+
translations?: DeletePopupTexts;
|
22
|
+
}
|
23
|
+
|
24
|
+
export const DeleteConfirmationModal = ({
|
25
|
+
translations
|
26
|
+
}: DeleteConfirmationModalProps) => {
|
27
|
+
const [error, setError] = useState<string | null>(null);
|
28
|
+
const { cards, deletion } = useAppSelector((state) => state.savedCard);
|
29
|
+
const dispatch = useAppDispatch();
|
30
|
+
|
31
|
+
const [deleteCard, { isLoading }] = useDeleteSavedCardMutation();
|
32
|
+
|
33
|
+
const handleClose = () => {
|
34
|
+
dispatch(setDeletionModalId(null));
|
35
|
+
dispatch(setDeletionModalVisible(false));
|
36
|
+
setError(null);
|
37
|
+
};
|
38
|
+
|
39
|
+
const handleDelete = async () => {
|
40
|
+
try {
|
41
|
+
await deleteCard(deletion.id).unwrap();
|
42
|
+
dispatch(setCards(cards.filter((card) => card.id !== deletion.id)));
|
43
|
+
handleClose();
|
44
|
+
} catch (error) {
|
45
|
+
setError(error.data.detail);
|
46
|
+
}
|
47
|
+
};
|
48
|
+
|
49
|
+
return (
|
50
|
+
<Modal
|
51
|
+
portalId="saved-card-remove-card-modal"
|
52
|
+
className="w-full sm:w-[28rem] max-h-[90vh] overflow-y-auto"
|
53
|
+
open={deletion.isModalVisible}
|
54
|
+
setOpen={() => dispatch(setDeletionModalVisible(false))}
|
55
|
+
>
|
56
|
+
<div className="px-6">
|
57
|
+
<h3 className="text-center mt-4 text-lg">
|
58
|
+
{translations?.title ?? defaultTranslations.title}
|
59
|
+
</h3>
|
60
|
+
<div className="flex flex-col gap-3 p-5 w-3/4 m-auto">
|
61
|
+
<Button className="py-3 h-auto" onClick={handleDelete}>
|
62
|
+
{isLoading ? (
|
63
|
+
<LoaderSpinner className="w-4 h-4" />
|
64
|
+
) : (
|
65
|
+
translations?.delete_button ?? defaultTranslations.delete_button
|
66
|
+
)}
|
67
|
+
</Button>
|
68
|
+
<Button
|
69
|
+
appearance="outlined"
|
70
|
+
className="underline px-5 py-3 h-auto"
|
71
|
+
onClick={handleClose}
|
72
|
+
>
|
73
|
+
{translations?.cancel_button ?? defaultTranslations.cancel_button}
|
74
|
+
</Button>
|
75
|
+
{error && <p className="text-error text-xs text-center">{error}</p>}
|
76
|
+
</div>
|
77
|
+
</div>
|
78
|
+
</Modal>
|
79
|
+
);
|
80
|
+
};
|
@@ -0,0 +1,22 @@
|
|
1
|
+
import SavedCardInstallments from './installments';
|
2
|
+
import React from 'react';
|
3
|
+
|
4
|
+
export const InstallmentSection = ({
|
5
|
+
title,
|
6
|
+
selectedCard,
|
7
|
+
installmentOptions,
|
8
|
+
translations,
|
9
|
+
errors
|
10
|
+
}) => (
|
11
|
+
<div className="border-solid border-gray-400 bg-white">
|
12
|
+
<div className="px-4 py-2">
|
13
|
+
<span className="text-black-800 text-lg font-medium">{title}</span>
|
14
|
+
</div>
|
15
|
+
<SavedCardInstallments
|
16
|
+
selectedCard={selectedCard}
|
17
|
+
installmentOptions={installmentOptions}
|
18
|
+
translations={translations}
|
19
|
+
error={errors}
|
20
|
+
/>
|
21
|
+
</div>
|
22
|
+
);
|
@@ -0,0 +1,113 @@
|
|
1
|
+
import { Price, Radio } from '@akinon/next/components';
|
2
|
+
import { useLocalization } from '@akinon/next/hooks';
|
3
|
+
import { useSetSavedCardInstallmentOptionMutation } from '../redux/api';
|
4
|
+
import { useEffect, useState } from 'react';
|
5
|
+
import { SavedCard } from '../redux/reducer';
|
6
|
+
import { InstallmentTexts } from '../types';
|
7
|
+
|
8
|
+
const defaultTranslations = {
|
9
|
+
payments: 'Payments',
|
10
|
+
per_month: 'Per Month',
|
11
|
+
total: 'Total'
|
12
|
+
};
|
13
|
+
|
14
|
+
type SavedCardInstallmentsProps = {
|
15
|
+
selectedCard: SavedCard;
|
16
|
+
installmentOptions: any[];
|
17
|
+
translations?: InstallmentTexts;
|
18
|
+
error: any;
|
19
|
+
};
|
20
|
+
|
21
|
+
const SavedCardInstallments = ({
|
22
|
+
selectedCard,
|
23
|
+
installmentOptions = [],
|
24
|
+
translations,
|
25
|
+
error
|
26
|
+
}: SavedCardInstallmentsProps) => {
|
27
|
+
const { t } = useLocalization();
|
28
|
+
const [installmentOption, setInstallmentOption] = useState(null);
|
29
|
+
const [setInstallment] = useSetSavedCardInstallmentOptionMutation();
|
30
|
+
|
31
|
+
useEffect(() => {
|
32
|
+
if (
|
33
|
+
selectedCard &&
|
34
|
+
installmentOptions.length > 0 &&
|
35
|
+
installmentOptions[0]?.pk !== installmentOption
|
36
|
+
) {
|
37
|
+
const firstOptionPk = installmentOptions[0].pk;
|
38
|
+
setInstallment({
|
39
|
+
installment: firstOptionPk
|
40
|
+
});
|
41
|
+
setInstallmentOption(firstOptionPk);
|
42
|
+
}
|
43
|
+
}, [installmentOptions, installmentOption, setInstallment, selectedCard]);
|
44
|
+
|
45
|
+
if (installmentOptions.length === 0) {
|
46
|
+
return (
|
47
|
+
<div className="text-xs text-black-800 p-4 sm:p-6">
|
48
|
+
{t('checkout.payment.installment_options.description')}
|
49
|
+
</div>
|
50
|
+
);
|
51
|
+
}
|
52
|
+
|
53
|
+
return (
|
54
|
+
<div>
|
55
|
+
<div className="px-4 mb-4 sm:px-6 sm:mb-6">
|
56
|
+
<table className="w-full border border-solid border-gray-400">
|
57
|
+
<thead>
|
58
|
+
<tr>
|
59
|
+
<th className="text-xs font-normal border-e border-solid border-gray-400 px-2 py-2 text-start">
|
60
|
+
{translations?.payments || defaultTranslations.payments}
|
61
|
+
</th>
|
62
|
+
<th className="text-xs font-normal border-e border-solid border-gray-400 px-2 py-2 text-right">
|
63
|
+
{translations?.per_month || defaultTranslations.per_month}
|
64
|
+
</th>
|
65
|
+
<th className="text-xs font-normal border-e border-solid border-gray-400 px-2 py-2 text-right">
|
66
|
+
{translations?.total || defaultTranslations.total}
|
67
|
+
</th>
|
68
|
+
</tr>
|
69
|
+
</thead>
|
70
|
+
<tbody>
|
71
|
+
{installmentOptions.map((option) => (
|
72
|
+
<tr
|
73
|
+
key={`installment-${option.pk}`}
|
74
|
+
className="border-t border-solid border-gray-400"
|
75
|
+
>
|
76
|
+
<td className="text-xs font-normal border-e border-solid border-gray-400 px-2 py-2 text-left">
|
77
|
+
<Radio
|
78
|
+
value={option.pk}
|
79
|
+
name="installment"
|
80
|
+
checked={option.pk === installmentOption}
|
81
|
+
onChange={() => {
|
82
|
+
setInstallment({
|
83
|
+
installment: option.pk
|
84
|
+
});
|
85
|
+
setInstallmentOption(option.pk);
|
86
|
+
}}
|
87
|
+
>
|
88
|
+
<span className="w-full flex items-center justify-start pl-2">
|
89
|
+
<span className="text-xs text-black-800 transition-all">
|
90
|
+
{option.label}
|
91
|
+
</span>
|
92
|
+
</span>
|
93
|
+
</Radio>
|
94
|
+
</td>
|
95
|
+
<td className="text-xs font-normal border-e border-solid border-gray-400 px-2 py-2 text-right">
|
96
|
+
<Price value={option.monthly_price_with_accrued_interest} />
|
97
|
+
</td>
|
98
|
+
<td className="text-xs font-normal border-e border-solid border-gray-400 px-2 py-2 text-right">
|
99
|
+
<Price value={option.price_with_accrued_interest} />
|
100
|
+
</td>
|
101
|
+
</tr>
|
102
|
+
))}
|
103
|
+
</tbody>
|
104
|
+
</table>
|
105
|
+
</div>
|
106
|
+
{error && (
|
107
|
+
<div className="px-6 mt-4 text-sm text-error">{error.message}</div>
|
108
|
+
)}
|
109
|
+
</div>
|
110
|
+
);
|
111
|
+
};
|
112
|
+
|
113
|
+
export default SavedCardInstallments;
|
package/src/index.tsx
CHANGED
@@ -1,6 +1,4 @@
|
|
1
|
-
export * from './views/option';
|
2
|
-
|
3
1
|
import savedCardReducer from './redux/reducer';
|
4
|
-
import
|
2
|
+
import SavedCardOption from './views/saved-card-option';
|
5
3
|
|
6
|
-
export { savedCardReducer,
|
4
|
+
export { savedCardReducer, SavedCardOption };
|
@@ -2,6 +2,7 @@ import { CheckoutContext, PreOrder } from '@akinon/next/types';
|
|
2
2
|
import { api } from '@akinon/next/data/client/api';
|
3
3
|
import { buildClientRequestUrl } from '@akinon/next/utils';
|
4
4
|
import { setPaymentStepBusy } from '@akinon/next/redux/reducers/checkout';
|
5
|
+
import { SavedCard } from './reducer';
|
5
6
|
|
6
7
|
interface CheckoutResponse {
|
7
8
|
pre_order?: PreOrder;
|
@@ -13,21 +14,55 @@ interface CheckoutResponse {
|
|
13
14
|
redirect_url?: string;
|
14
15
|
}
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
17
|
+
interface GetResponse<T> {
|
18
|
+
count: number;
|
19
|
+
next: null;
|
20
|
+
previous: null;
|
21
|
+
results: T[];
|
22
|
+
}
|
23
|
+
|
24
|
+
type SetSavedCardRequest = {
|
25
|
+
card: SavedCard;
|
26
|
+
};
|
27
|
+
|
28
|
+
type DeleteSavedCardRequest = number;
|
29
|
+
|
30
|
+
type SetInstallmentRequest = {
|
31
|
+
installment: string;
|
32
|
+
};
|
33
|
+
|
34
|
+
type CompleteSavedCardRequest = {
|
35
|
+
agreement: boolean;
|
36
|
+
};
|
26
37
|
|
27
38
|
export const savedCardApi = api.injectEndpoints({
|
28
39
|
endpoints: (build) => ({
|
29
|
-
|
30
|
-
query: (
|
40
|
+
getSavedCards: build.query<GetResponse<SavedCard>, void>({
|
41
|
+
query: () => ({
|
42
|
+
url: buildClientRequestUrl('/users/saved-cards/')
|
43
|
+
}),
|
44
|
+
async onQueryStarted(arg, { dispatch, queryFulfilled }) {
|
45
|
+
dispatch(setPaymentStepBusy(true));
|
46
|
+
await queryFulfilled;
|
47
|
+
dispatch(setPaymentStepBusy(false));
|
48
|
+
}
|
49
|
+
}),
|
50
|
+
deleteSavedCard: build.mutation<
|
51
|
+
GetResponse<SavedCard>,
|
52
|
+
DeleteSavedCardRequest
|
53
|
+
>({
|
54
|
+
query: (cardId) => ({
|
55
|
+
url: buildClientRequestUrl(`/users/saved-cards/${cardId}`),
|
56
|
+
method: 'DELETE'
|
57
|
+
}),
|
58
|
+
async onQueryStarted(arg, { dispatch, queryFulfilled }) {
|
59
|
+
dispatch(setPaymentStepBusy(true));
|
60
|
+
await queryFulfilled;
|
61
|
+
dispatch(setPaymentStepBusy(false));
|
62
|
+
}
|
63
|
+
}),
|
64
|
+
setSavedCard: build.mutation<CheckoutResponse, SetSavedCardRequest>({
|
65
|
+
query: ({ card }) => ({
|
31
66
|
url: buildClientRequestUrl(
|
32
67
|
`/orders/checkout?page=SavedCardSelectionPage`,
|
33
68
|
{
|
@@ -36,17 +71,20 @@ export const savedCardApi = api.injectEndpoints({
|
|
36
71
|
),
|
37
72
|
method: 'POST',
|
38
73
|
body: {
|
39
|
-
card:
|
74
|
+
card: card.token
|
40
75
|
}
|
41
76
|
}),
|
42
|
-
async onQueryStarted(
|
77
|
+
async onQueryStarted({ card }, { dispatch, queryFulfilled }) {
|
43
78
|
dispatch(setPaymentStepBusy(true));
|
44
79
|
await queryFulfilled;
|
45
80
|
dispatch(setPaymentStepBusy(false));
|
46
81
|
}
|
47
82
|
}),
|
48
|
-
|
49
|
-
|
83
|
+
setSavedCardInstallmentOption: build.mutation<
|
84
|
+
CheckoutResponse,
|
85
|
+
SetInstallmentRequest
|
86
|
+
>({
|
87
|
+
query: ({ installment }) => ({
|
50
88
|
url: buildClientRequestUrl(
|
51
89
|
`/orders/checkout?page=SavedCardInstallmentSelectionPage`,
|
52
90
|
{
|
@@ -55,7 +93,7 @@ export const savedCardApi = api.injectEndpoints({
|
|
55
93
|
),
|
56
94
|
method: 'POST',
|
57
95
|
body: {
|
58
|
-
installment
|
96
|
+
installment
|
59
97
|
}
|
60
98
|
}),
|
61
99
|
async onQueryStarted(arg, { dispatch, queryFulfilled }) {
|
@@ -64,19 +102,21 @@ export const savedCardApi = api.injectEndpoints({
|
|
64
102
|
dispatch(setPaymentStepBusy(false));
|
65
103
|
}
|
66
104
|
}),
|
67
|
-
|
105
|
+
completeSavedCard: build.mutation<
|
68
106
|
CheckoutResponse,
|
69
107
|
CompleteSavedCardRequest
|
70
108
|
>({
|
71
|
-
query: (
|
109
|
+
query: ({ agreement }) => ({
|
72
110
|
url: buildClientRequestUrl(
|
73
|
-
`/orders/checkout?page=
|
111
|
+
`/orders/checkout?page=CompleteSavedCardRequest`,
|
74
112
|
{
|
75
113
|
useFormData: true
|
76
114
|
}
|
77
115
|
),
|
78
116
|
method: 'POST',
|
79
|
-
body:
|
117
|
+
body: {
|
118
|
+
agreement
|
119
|
+
}
|
80
120
|
}),
|
81
121
|
async onQueryStarted(arg, { dispatch, queryFulfilled }) {
|
82
122
|
dispatch(setPaymentStepBusy(true));
|
@@ -88,7 +128,9 @@ export const savedCardApi = api.injectEndpoints({
|
|
88
128
|
});
|
89
129
|
|
90
130
|
export const {
|
131
|
+
useGetSavedCardsQuery,
|
91
132
|
useSetSavedCardMutation,
|
92
|
-
|
93
|
-
|
133
|
+
useSetSavedCardInstallmentOptionMutation,
|
134
|
+
useCompleteSavedCardMutation,
|
135
|
+
useDeleteSavedCardMutation
|
94
136
|
} = savedCardApi;
|
package/src/redux/reducer.ts
CHANGED
@@ -1,33 +1,45 @@
|
|
1
|
-
'use client';
|
2
|
-
|
3
1
|
import { createSlice } from '@reduxjs/toolkit';
|
4
2
|
|
3
|
+
export type SavedCard = {
|
4
|
+
id: number;
|
5
|
+
name: string;
|
6
|
+
masked_card_number: string;
|
7
|
+
token: string;
|
8
|
+
};
|
9
|
+
|
5
10
|
export interface SavedCardState {
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
ucsToken?: string;
|
11
|
+
cards?: Array<SavedCard>;
|
12
|
+
deletion: {
|
13
|
+
id: number;
|
14
|
+
isModalVisible: boolean;
|
11
15
|
};
|
12
16
|
}
|
13
17
|
|
14
18
|
const initialState: SavedCardState = {
|
15
|
-
|
19
|
+
cards: undefined,
|
20
|
+
deletion: {
|
21
|
+
id: null,
|
22
|
+
isModalVisible: false
|
23
|
+
}
|
16
24
|
};
|
17
25
|
|
18
26
|
const savedCardSlice = createSlice({
|
19
27
|
name: 'savedCard',
|
20
28
|
initialState,
|
21
29
|
reducers: {
|
22
|
-
|
23
|
-
state.
|
30
|
+
setCards(state, { payload }) {
|
31
|
+
state.cards = payload;
|
32
|
+
},
|
33
|
+
setDeletionModalId(state, { payload }) {
|
34
|
+
state.deletion.id = payload;
|
24
35
|
},
|
25
|
-
|
26
|
-
|
36
|
+
setDeletionModalVisible(state, { payload }) {
|
37
|
+
state.deletion.isModalVisible = payload;
|
27
38
|
}
|
28
39
|
}
|
29
40
|
});
|
30
41
|
|
31
|
-
export const {
|
42
|
+
export const { setCards, setDeletionModalId, setDeletionModalVisible } =
|
43
|
+
savedCardSlice.actions;
|
32
44
|
|
33
45
|
export default savedCardSlice.reducer;
|