@akinon/pz-masterpass 2.0.0-beta.2 → 2.0.0-beta.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +124 -6
- package/README.md +466 -6
- package/package.json +7 -7
- package/src/redux/reducer.ts +15 -1
- package/src/views/card-list/index.tsx +124 -80
- package/src/views/card-registration/index.tsx +69 -8
- package/src/views/delete-confirmation-modal/index.tsx +26 -1
- package/src/views/link-modal/index.tsx +24 -2
- package/src/views/otp-modal/index.tsx +34 -4
- package/src/views/otp-modal/otp-form.tsx +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,18 +1,136 @@
|
|
|
1
1
|
# @akinon/pz-masterpass
|
|
2
2
|
|
|
3
|
-
## 2.0.0-beta.
|
|
3
|
+
## 2.0.0-beta.20
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 1.125.0
|
|
6
|
+
|
|
7
|
+
## 1.124.0
|
|
8
|
+
|
|
9
|
+
## 1.123.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 572f4f7b: ZERO-3959: Add binNumberConfig prop to MasterpassCardList for customizable BIN extraction
|
|
14
|
+
- 88cb4f62: ZERO-3959: Update onSelectCard to handle masked BIN extraction based on card value format
|
|
15
|
+
|
|
16
|
+
## 1.122.0
|
|
17
|
+
|
|
18
|
+
## 1.121.0
|
|
19
|
+
|
|
20
|
+
## 1.120.0
|
|
21
|
+
|
|
22
|
+
### Minor Changes
|
|
23
|
+
|
|
24
|
+
- 6ad72e8d: ZERO-4032: Add loading state management for payment submissions across multiple components and add safe guarding
|
|
25
|
+
|
|
26
|
+
## 1.119.0
|
|
27
|
+
|
|
28
|
+
## 1.118.0
|
|
29
|
+
|
|
30
|
+
## 1.117.0
|
|
31
|
+
|
|
32
|
+
## 1.116.0
|
|
33
|
+
|
|
34
|
+
## 1.115.0
|
|
35
|
+
|
|
36
|
+
## 1.114.0
|
|
37
|
+
|
|
38
|
+
## 1.113.0
|
|
39
|
+
|
|
40
|
+
## 1.112.0
|
|
41
|
+
|
|
42
|
+
## 1.111.0
|
|
43
|
+
|
|
44
|
+
## 1.110.0
|
|
45
|
+
|
|
46
|
+
## 1.109.0
|
|
47
|
+
|
|
48
|
+
## 1.108.0
|
|
49
|
+
|
|
50
|
+
## 1.107.0
|
|
51
|
+
|
|
52
|
+
## 1.106.0
|
|
53
|
+
|
|
54
|
+
## 1.105.0
|
|
55
|
+
|
|
56
|
+
## 1.104.0
|
|
57
|
+
|
|
58
|
+
## 1.103.0
|
|
59
|
+
|
|
60
|
+
### Minor Changes
|
|
61
|
+
|
|
62
|
+
- ad0bec9: ZERO-3364 :Update readme file for masterpass
|
|
63
|
+
- b31333e: ZERO-3357 :Add components with renderer properties for customizable UI elements in pz-masterpass
|
|
64
|
+
|
|
65
|
+
## 1.102.0
|
|
66
|
+
|
|
67
|
+
## 1.101.0
|
|
68
|
+
|
|
69
|
+
## 1.100.0
|
|
70
|
+
|
|
71
|
+
### Minor Changes
|
|
72
|
+
|
|
73
|
+
- e57cd93: ZERO-3460: prevent installment loop on payment method switch
|
|
74
|
+
|
|
75
|
+
## 1.99.0
|
|
76
|
+
|
|
77
|
+
### Minor Changes
|
|
78
|
+
|
|
79
|
+
- d58538b: ZERO-3638: Enhance RC pipeline: add fetch, merge, and pre-release setup with conditional commit
|
|
80
|
+
|
|
81
|
+
## 1.98.0
|
|
82
|
+
|
|
83
|
+
## 1.97.0
|
|
84
|
+
|
|
85
|
+
## 1.96.0
|
|
86
|
+
|
|
87
|
+
## 1.95.0
|
|
88
|
+
|
|
89
|
+
## 1.94.0
|
|
90
|
+
|
|
91
|
+
## 1.93.0
|
|
92
|
+
|
|
93
|
+
## 1.92.0
|
|
94
|
+
|
|
95
|
+
## 1.91.0
|
|
96
|
+
|
|
97
|
+
## 1.90.0
|
|
98
|
+
|
|
99
|
+
## 1.89.0
|
|
100
|
+
|
|
101
|
+
## 1.88.0
|
|
102
|
+
|
|
103
|
+
## 1.87.0
|
|
104
|
+
|
|
105
|
+
## 1.86.0
|
|
106
|
+
|
|
107
|
+
## 1.85.0
|
|
108
|
+
|
|
109
|
+
## 1.84.0
|
|
6
110
|
|
|
7
111
|
### Minor Changes
|
|
8
112
|
|
|
9
|
-
- ZERO-
|
|
113
|
+
- 624a4eb: ZERO-3276: Update installation instructions across multiple README files to standardize format and improve clarity
|
|
114
|
+
|
|
115
|
+
## 1.83.0
|
|
116
|
+
|
|
117
|
+
## 1.82.0
|
|
118
|
+
|
|
119
|
+
## 1.81.0
|
|
120
|
+
|
|
121
|
+
### Minor Changes
|
|
122
|
+
|
|
123
|
+
- fdbf156: ZERO-3137: Trim whitespace from account alias name input in card registration form
|
|
124
|
+
|
|
125
|
+
## 1.80.0
|
|
126
|
+
|
|
127
|
+
## 1.79.0
|
|
10
128
|
|
|
11
|
-
##
|
|
129
|
+
## 1.78.0
|
|
12
130
|
|
|
13
|
-
|
|
131
|
+
## 1.77.0
|
|
14
132
|
|
|
15
|
-
|
|
133
|
+
## 1.76.0
|
|
16
134
|
|
|
17
135
|
## 1.75.0
|
|
18
136
|
|
package/README.md
CHANGED
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
# @akinon/pz-masterpass
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## Installation method
|
|
4
|
+
|
|
5
|
+
You can use the following command to install the extension with the latest plugins:
|
|
4
6
|
|
|
5
7
|
```bash
|
|
6
|
-
# For latest version
|
|
7
|
-
yarn add @akinon/pz-masterpass
|
|
8
8
|
|
|
9
|
-
# Preferred installation method
|
|
10
9
|
npx @akinon/projectzero@latest --plugins
|
|
10
|
+
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
## Available Props
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
## Masterpass Provider
|
|
16
16
|
|
|
17
17
|
##### File Path: src/app/[commerce]/[locale]/[currency]/orders/checkout/page.tsx
|
|
18
18
|
|
|
19
|
+
### Usage Examples
|
|
20
|
+
|
|
21
|
+
##### Default Usage
|
|
22
|
+
|
|
19
23
|
```javascript
|
|
20
24
|
<PluginModule
|
|
21
25
|
component={Component.MasterpassProvider}
|
|
@@ -29,7 +33,7 @@ npx @akinon/projectzero@latest --plugins
|
|
|
29
33
|
</PluginModule>
|
|
30
34
|
```
|
|
31
35
|
|
|
32
|
-
|
|
36
|
+
##### Customized Usage with Additional Params
|
|
33
37
|
|
|
34
38
|
##### To add extra parameters, it can be added by passing additional Params property to the Masterpass Provider.
|
|
35
39
|
|
|
@@ -51,8 +55,30 @@ npx @akinon/projectzero@latest --plugins
|
|
|
51
55
|
|
|
52
56
|
### Delete Confirmation Modal
|
|
53
57
|
|
|
58
|
+
#### Props
|
|
59
|
+
|
|
60
|
+
| Props | Type | Required | Description |
|
|
61
|
+
| --- | --- | --- | --- |
|
|
62
|
+
| translations | typeof defaultTranslations | Optional | Used to customize the default texts. |
|
|
63
|
+
| renderer | RendererProps | Optional | Renderer functions that can be used to customize all subcomponents. |
|
|
64
|
+
|
|
65
|
+
#### RendererProps
|
|
66
|
+
|
|
67
|
+
| Props | Type | Description |
|
|
68
|
+
| --- | --- | --- |
|
|
69
|
+
| open | boolean | It is information whether the modal is open or not. |
|
|
70
|
+
| setOpen | (open: boolean) => void | Used to manage modal on/off operation. |
|
|
71
|
+
| onConfirm | () => void | This is the function that will perform the deletion operation. |
|
|
72
|
+
| onCancel | () => void | This is the function that will be run if the user cancels the deletion process. |
|
|
73
|
+
| loading | boolean | Indicates the loading status during the deletion process. |
|
|
74
|
+
| error | string / null | Error message to be displayed if an error occurs during deletion. |
|
|
75
|
+
|
|
54
76
|
##### File Path: src/app/[commerce]/[locale]/[currency]/orders/checkout/page.tsx
|
|
55
77
|
|
|
78
|
+
### Usage Examples
|
|
79
|
+
|
|
80
|
+
##### Default Usage
|
|
81
|
+
|
|
56
82
|
```javascript
|
|
57
83
|
<PluginModule
|
|
58
84
|
component={Component.MasterpassDeleteConfirmationModal}
|
|
@@ -66,10 +92,70 @@ npx @akinon/projectzero@latest --plugins
|
|
|
66
92
|
/>
|
|
67
93
|
```
|
|
68
94
|
|
|
95
|
+
##### Customized Usage with Renderer
|
|
96
|
+
|
|
97
|
+
```javascript
|
|
98
|
+
<PluginModule
|
|
99
|
+
component={Component.MasterpassDeleteConfirmationModal}
|
|
100
|
+
props={{
|
|
101
|
+
renderer: {
|
|
102
|
+
Content: ({ open, setOpen, onConfirm, onCancel, loading, error }) => (
|
|
103
|
+
<Modal
|
|
104
|
+
open={open}
|
|
105
|
+
setOpen={setOpen}
|
|
106
|
+
className="w-full sm:w-[28rem]"
|
|
107
|
+
portalId="masterpass-remove-card-modal"
|
|
108
|
+
>
|
|
109
|
+
<div className="flex flex-col items-center p-5">
|
|
110
|
+
<div className="text-xs mb-3">
|
|
111
|
+
Are you sure you want to delete your card?
|
|
112
|
+
</div>
|
|
113
|
+
<Button onClick={onConfirm} className="bg-red-600 text-black">
|
|
114
|
+
{loading ? 'Deletion...' : 'Delete'}
|
|
115
|
+
</Button>
|
|
116
|
+
<Button appearance="outlined" onClick={onCancel}>
|
|
117
|
+
Cancel
|
|
118
|
+
</Button>
|
|
119
|
+
{error && (
|
|
120
|
+
<p className="text-red-500 text-sm mt-2 text-center">
|
|
121
|
+
🚨 {error}
|
|
122
|
+
</p>
|
|
123
|
+
)}
|
|
124
|
+
</div>
|
|
125
|
+
</Modal>
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
}}
|
|
129
|
+
/>
|
|
130
|
+
```
|
|
131
|
+
|
|
69
132
|
### OTP Modal
|
|
70
133
|
|
|
134
|
+
#### Props
|
|
135
|
+
|
|
136
|
+
| Props | Type | Required | Description |
|
|
137
|
+
| --- | --- | --- | --- |
|
|
138
|
+
| translations | { [key: string]: string } | Optional | Translation object to customize all texts. Overrides default texts. |
|
|
139
|
+
| renderer | RendererProps | Optional | Renderer functions that can be used to customize all subcomponents. |
|
|
140
|
+
|
|
141
|
+
#### RendererProps
|
|
142
|
+
|
|
143
|
+
| Props | Type | Description |
|
|
144
|
+
| --- | --- | --- |
|
|
145
|
+
| open | boolean | It is information whether the modal is open or not. |
|
|
146
|
+
| setOpen | (open: boolean) => void | Used to manage modal on/off operation. |
|
|
147
|
+
| onSubmit | (data: { otp_code: string }) => void | Sends the OTP code entered by the user. |
|
|
148
|
+
| loading | boolean | Indicates the loading status during validation. |
|
|
149
|
+
| error | string / null | Error message to be displayed if there is an error. |
|
|
150
|
+
| resendSms | () => void | Function triggered when the user requests SMS again. |
|
|
151
|
+
| resendSmsFetching | boolean | Indicates the loading status during the resending process. |
|
|
152
|
+
| targetDate | number | When the countdown will end (as a timestamp). |
|
|
153
|
+
| otpRef | string / null | Transaction reference number, if applicable. |
|
|
154
|
+
|
|
71
155
|
##### File Path: src/app/[commerce]/[locale]/[currency]/orders/checkout/page.tsx
|
|
72
156
|
|
|
157
|
+
##### Default Usage
|
|
158
|
+
|
|
73
159
|
```javascript
|
|
74
160
|
<PluginModule
|
|
75
161
|
component={Component.MasterpassOtpModal}
|
|
@@ -86,10 +172,127 @@ npx @akinon/projectzero@latest --plugins
|
|
|
86
172
|
/>
|
|
87
173
|
```
|
|
88
174
|
|
|
175
|
+
##### Customized Usage with Renderer
|
|
176
|
+
|
|
177
|
+
```javascript
|
|
178
|
+
<PluginModule
|
|
179
|
+
component={Component.MasterpassOtpModal}
|
|
180
|
+
props={{
|
|
181
|
+
renderer: {
|
|
182
|
+
Content: ({
|
|
183
|
+
open,
|
|
184
|
+
setOpen,
|
|
185
|
+
onSubmit,
|
|
186
|
+
loading,
|
|
187
|
+
error,
|
|
188
|
+
resendSms,
|
|
189
|
+
resendSmsFetching,
|
|
190
|
+
targetDate,
|
|
191
|
+
otpRef
|
|
192
|
+
}) => {
|
|
193
|
+
const [code, setCode] = useState('');
|
|
194
|
+
const [remainingSeconds, setRemainingSeconds] = useState(
|
|
195
|
+
Math.ceil((targetDate - Date.now()) / 1000)
|
|
196
|
+
);
|
|
197
|
+
useEffect(() => {
|
|
198
|
+
if (!targetDate) return;
|
|
199
|
+
const interval = setInterval(() => {
|
|
200
|
+
const secondsLeft = Math.ceil((targetDate - Date.now()) / 1000);
|
|
201
|
+
setRemainingSeconds(secondsLeft);
|
|
202
|
+
if (secondsLeft <= 0) {
|
|
203
|
+
clearInterval(interval);
|
|
204
|
+
}
|
|
205
|
+
}, 1000);
|
|
206
|
+
return () => clearInterval(interval);
|
|
207
|
+
}, [targetDate]);
|
|
208
|
+
return (
|
|
209
|
+
<Modal
|
|
210
|
+
open={open}
|
|
211
|
+
setOpen={setOpen}
|
|
212
|
+
className="w-full sm:w-[28rem]"
|
|
213
|
+
portalId="otp-masterpass"
|
|
214
|
+
>
|
|
215
|
+
<div className="px-6 py-4">
|
|
216
|
+
<h2 className="text-center text-xl text-primary-600 font-semibold">
|
|
217
|
+
Verification Required
|
|
218
|
+
</h2>
|
|
219
|
+
<form
|
|
220
|
+
onSubmit={(e) => {
|
|
221
|
+
e.preventDefault();
|
|
222
|
+
onSubmit({ otp_code: code });
|
|
223
|
+
}}
|
|
224
|
+
className="flex flex-col items-center mt-4"
|
|
225
|
+
>
|
|
226
|
+
<label
|
|
227
|
+
htmlFor="otp_code"
|
|
228
|
+
className="text-sm mb-1 text-gray-700"
|
|
229
|
+
>
|
|
230
|
+
SMS Code
|
|
231
|
+
</label>
|
|
232
|
+
<input
|
|
233
|
+
id="otp_code"
|
|
234
|
+
maxLength={6}
|
|
235
|
+
value={code}
|
|
236
|
+
onChange={(e) => setCode(e.target.value)}
|
|
237
|
+
placeholder="••••••"
|
|
238
|
+
className="border p-2 rounded w-full max-w-[200px] text-center tracking-widest"
|
|
239
|
+
/>
|
|
240
|
+
<Button
|
|
241
|
+
type="submit"
|
|
242
|
+
className="mt-4 px-4 py-2 bg-blue-600 text-black rounded"
|
|
243
|
+
>
|
|
244
|
+
Verify
|
|
245
|
+
</Button>
|
|
246
|
+
{error && <p className="mt-2 text-xs text-red-500">{error}</p>}
|
|
247
|
+
</form>
|
|
248
|
+
<div className="mt-2 flex justify-center text-sm text-gray-600">
|
|
249
|
+
{remainingSeconds > 0
|
|
250
|
+
? `Time remaining: ${remainingSeconds} seconds`
|
|
251
|
+
: 'Time is up'}
|
|
252
|
+
</div>
|
|
253
|
+
<div className="mt-2 flex justify-center">
|
|
254
|
+
<Button
|
|
255
|
+
onClick={resendSms}
|
|
256
|
+
disabled={loading}
|
|
257
|
+
className="mt-4 text-sm underline text-secondary-500"
|
|
258
|
+
>
|
|
259
|
+
{resendSmsFetching ? 'Code sent' : 'Resend SMS code'}
|
|
260
|
+
</Button>
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
</Modal>
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}}
|
|
268
|
+
/>
|
|
269
|
+
```
|
|
270
|
+
|
|
89
271
|
### Link Modal
|
|
90
272
|
|
|
273
|
+
#### Props
|
|
274
|
+
|
|
275
|
+
| Props | Type | Required | Description |
|
|
276
|
+
| --- | --- | --- | --- |
|
|
277
|
+
| translations | { [key: string]: string } | Optional | Translation object to customize all texts. Overrides default texts. |
|
|
278
|
+
| renderer | RendererProps | Optional | Renderer functions that can be used to customize all subcomponents. |
|
|
279
|
+
|
|
280
|
+
#### RendererProps
|
|
281
|
+
|
|
282
|
+
| Props | Type | Description |
|
|
283
|
+
| --- | --- | --- |
|
|
284
|
+
| open | boolean | It is information whether the modal is open or not. |
|
|
285
|
+
| setOpen | (open: boolean) => void | Used to manage modal on/off operation. |
|
|
286
|
+
| onClick | () => void | It is the function that is called when the user clicks the "Use" button. |
|
|
287
|
+
| loading | boolean | This is the loading status that will be displayed during the API request. |
|
|
288
|
+
| error | string | If there is an error in the API response, a message is displayed in this field. |
|
|
289
|
+
|
|
91
290
|
##### File Path: src/app/[commerce]/[locale]/[currency]/orders/checkout/page.tsx
|
|
92
291
|
|
|
292
|
+
### Usage Examples
|
|
293
|
+
|
|
294
|
+
##### Default Usage
|
|
295
|
+
|
|
93
296
|
```javascript
|
|
94
297
|
<PluginModule
|
|
95
298
|
component={Component.MasterpassLinkModal}
|
|
@@ -103,10 +306,67 @@ npx @akinon/projectzero@latest --plugins
|
|
|
103
306
|
/>
|
|
104
307
|
```
|
|
105
308
|
|
|
309
|
+
##### Customized Usage with Renderer
|
|
310
|
+
|
|
311
|
+
```javascript
|
|
312
|
+
<PluginModule
|
|
313
|
+
component={Component.MasterpassLinkModal}
|
|
314
|
+
props={{
|
|
315
|
+
renderer: {
|
|
316
|
+
Content: ({ open, setOpen, onClick, loading, error }) => (
|
|
317
|
+
<Modal
|
|
318
|
+
open={open}
|
|
319
|
+
setOpen={setOpen}
|
|
320
|
+
className="w-full sm:w-[28rem]"
|
|
321
|
+
portalId="masterpass-check-user"
|
|
322
|
+
>
|
|
323
|
+
<div className="p-10">
|
|
324
|
+
<p className="text-sm text-center">
|
|
325
|
+
You have cards registered to your Masterpass account. Do you want
|
|
326
|
+
to use your cards?
|
|
327
|
+
</p>
|
|
328
|
+
<Button
|
|
329
|
+
onClick={onClick}
|
|
330
|
+
disabled={loading}
|
|
331
|
+
className="bg-green-600 text-error w-full py-2 mt-4"
|
|
332
|
+
>
|
|
333
|
+
{loading ? 'Loading...' : 'Use'}
|
|
334
|
+
</Button>
|
|
335
|
+
{error && <p className="text-error text-sm">{error}</p>}
|
|
336
|
+
</div>
|
|
337
|
+
</Modal>
|
|
338
|
+
)
|
|
339
|
+
}
|
|
340
|
+
}}
|
|
341
|
+
/>
|
|
342
|
+
```
|
|
343
|
+
|
|
106
344
|
### Card List
|
|
107
345
|
|
|
346
|
+
#### Props
|
|
347
|
+
|
|
348
|
+
| Props | Type | Required | Description |
|
|
349
|
+
| --- | --- | --- | --- |
|
|
350
|
+
| className | string | Optional | Gives an additional style class to the component's outer container. |
|
|
351
|
+
| translations | { [key: string]: string } | Optional | Translation object to customize all texts. Overrides default texts. |
|
|
352
|
+
| renderer | RendererProps | Optional | Renderer functions that can be used to customize all subcomponents. |
|
|
353
|
+
|
|
354
|
+
#### RendererProps
|
|
355
|
+
|
|
356
|
+
| Props | Type | Description |
|
|
357
|
+
| --- | --- | --- |
|
|
358
|
+
| Header | JSX.Element | Used to customize the title area at the top of the card list. |
|
|
359
|
+
| CardItem | (params: { card, selectedCard, onSelect, DeleteButton }) => JSX.Element | It allows you to fully customize the card element. |
|
|
360
|
+
| Loader | () => JSX.Element | Used to define a custom loader component to be displayed when loading cards. |
|
|
361
|
+
| ErrorFallback | (params: { error: string; onRetry: () => void }) => JSX.Element | Custom widget to be shown if an error occurs while loading cards. |
|
|
362
|
+
| SwitchPaymentButton | (props: { onClick: () => void; label: string }) => JSX.Element | Used to customize the "Pay with a new card" option. |
|
|
363
|
+
|
|
108
364
|
##### File Path: src/views/checkout/steps/payment/options/credit-card/index.tsx
|
|
109
365
|
|
|
366
|
+
### Usage Examples
|
|
367
|
+
|
|
368
|
+
##### Default Usage
|
|
369
|
+
|
|
110
370
|
```javascript
|
|
111
371
|
<PluginModule
|
|
112
372
|
component={Component.MasterpassCardList}
|
|
@@ -121,10 +381,127 @@ npx @akinon/projectzero@latest --plugins
|
|
|
121
381
|
/>
|
|
122
382
|
```
|
|
123
383
|
|
|
384
|
+
##### Customized Usage with Renderer
|
|
385
|
+
|
|
386
|
+
```javascript
|
|
387
|
+
<PluginModule
|
|
388
|
+
component={Component.MasterpassCardList}
|
|
389
|
+
props={{
|
|
390
|
+
className: 'px-10',
|
|
391
|
+
form: {
|
|
392
|
+
control,
|
|
393
|
+
register,
|
|
394
|
+
errors,
|
|
395
|
+
setFormValue,
|
|
396
|
+
clearErrors
|
|
397
|
+
},
|
|
398
|
+
renderer: {
|
|
399
|
+
Header: () => (
|
|
400
|
+
<div className="flex items-center gap-2 mt-4 px-10 text-success">
|
|
401
|
+
Select a card to pay
|
|
402
|
+
</div>
|
|
403
|
+
),
|
|
404
|
+
Loader: () => (
|
|
405
|
+
<div className="flex justify-center items-center p-10">
|
|
406
|
+
<span className="text-lg font-bold text-error">
|
|
407
|
+
Loading your cards...
|
|
408
|
+
</span>
|
|
409
|
+
</div>
|
|
410
|
+
),
|
|
411
|
+
ErrorFallback: ({ error, onRetry }) => (
|
|
412
|
+
<div className="text-center p-8">
|
|
413
|
+
<h2 className="text-red-500 text-xl mb-4">Oops! {error}</h2>
|
|
414
|
+
<Button onClick={onRetry}>Try Again</Button>
|
|
415
|
+
</div>
|
|
416
|
+
),
|
|
417
|
+
CardItem: ({ card, onSelect, DeleteButton, selectedCard }) => (
|
|
418
|
+
<li
|
|
419
|
+
key={card.UniqueId}
|
|
420
|
+
className="p-4 cursor-pointer rounded-[10px] border-gray border space-x-5 pl-5 mb-6 md:mb-2.5"
|
|
421
|
+
onClick={() => onSelect(card)}
|
|
422
|
+
>
|
|
423
|
+
<div className="flex justify-between items-center">
|
|
424
|
+
<div className="flex items-center">
|
|
425
|
+
<Checkbox
|
|
426
|
+
key={
|
|
427
|
+
selectedCard?.UniqueId === card.UniqueId
|
|
428
|
+
? 'selected'
|
|
429
|
+
: 'not-selected'
|
|
430
|
+
}
|
|
431
|
+
checked={selectedCard?.UniqueId === card.UniqueId}
|
|
432
|
+
onChange={() => onSelect(card)}
|
|
433
|
+
className="mr-2"
|
|
434
|
+
/>
|
|
435
|
+
<div>
|
|
436
|
+
<h3 className="font-semibold">{card.Name}</h3>
|
|
437
|
+
<p className="text-xs text-gray-500">{card.Value1}</p>
|
|
438
|
+
</div>
|
|
439
|
+
</div>
|
|
440
|
+
<DeleteButton cardAliasName={card.Name} />
|
|
441
|
+
</div>
|
|
442
|
+
</li>
|
|
443
|
+
),
|
|
444
|
+
SwitchPaymentButton: ({ onClick, label }) => (
|
|
445
|
+
<div className="px-10">
|
|
446
|
+
<button
|
|
447
|
+
onClick={onClick}
|
|
448
|
+
className="p-4 w-full text-left cursor-pointer rounded-[10px] border-gray border space-x-5 pl-5 mb-6 md:mb-2.5"
|
|
449
|
+
>
|
|
450
|
+
{label}
|
|
451
|
+
</button>
|
|
452
|
+
</div>
|
|
453
|
+
)
|
|
454
|
+
}
|
|
455
|
+
}}
|
|
456
|
+
/>
|
|
457
|
+
```
|
|
458
|
+
|
|
124
459
|
### Card Registration
|
|
125
460
|
|
|
461
|
+
#### Props
|
|
462
|
+
|
|
463
|
+
| Props | Type | Required | Description |
|
|
464
|
+
| --- | --- | --- | --- |
|
|
465
|
+
| getValues | () => Record<string, string> | Required | Returns the values of form fields. |
|
|
466
|
+
| className | string | Optional | Gives an additional CSS class to the outer container. |
|
|
467
|
+
| infoModalContent | React.ReactNode | Optional | Body content for the informational modal. |
|
|
468
|
+
| infoModalIcon | React.ReactNode | Optional | Used to override the notification icon. |
|
|
469
|
+
| translations | typeof defaultTranslations | Optional | Can be used to override default texts. |
|
|
470
|
+
| renderer | RendererProps | Optional | Renderer functions that can be used to customize all subcomponents. |
|
|
471
|
+
|
|
472
|
+
#### RendererProps
|
|
473
|
+
|
|
474
|
+
| Props | Type | Description |
|
|
475
|
+
| --- | --- | --- |
|
|
476
|
+
| Content | (props: { isChecked, toggle, setIsInfoModalOpen, onChange, onSubmit, loading, error, showGoBackLink?, onClickGoBackLink? }) => JSX.Element | Used to render the main content. |
|
|
477
|
+
| InfoModal | (props: { open: boolean; setOpen: (value: boolean) => void }) => JSX.Element | Can be used to override the notification modal. |
|
|
478
|
+
|
|
126
479
|
##### File Path: src/views/checkout/steps/payment/options/credit-card/index.tsx
|
|
127
480
|
|
|
481
|
+
### Usage Examples
|
|
482
|
+
|
|
483
|
+
##### Default Usage
|
|
484
|
+
|
|
485
|
+
```javascript
|
|
486
|
+
<PluginModule
|
|
487
|
+
component={Component.MasterpassCardRegistration}
|
|
488
|
+
props={{
|
|
489
|
+
// Do not remove getValues, it is used to get the form values
|
|
490
|
+
getValues,
|
|
491
|
+
translations: {
|
|
492
|
+
enter_card_name: 'Enter card name',
|
|
493
|
+
continue: 'Continue',
|
|
494
|
+
pay_with_my_masterpass_card: 'Pay with my Masterpass card',
|
|
495
|
+
terms_and_conditions: 'Masterpass terms and conditions',
|
|
496
|
+
card_registration_consent:
|
|
497
|
+
'I want to store my card information in the Mastercard infrastructure and use it again in my next purchase.'
|
|
498
|
+
}
|
|
499
|
+
}}
|
|
500
|
+
/>
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
##### Customized Usage with className, infoModalContent, infoModalIcon
|
|
504
|
+
|
|
128
505
|
```javascript
|
|
129
506
|
<PluginModule
|
|
130
507
|
component={Component.MasterpassCardRegistration}
|
|
@@ -145,3 +522,86 @@ npx @akinon/projectzero@latest --plugins
|
|
|
145
522
|
}}
|
|
146
523
|
/>
|
|
147
524
|
```
|
|
525
|
+
|
|
526
|
+
##### Customized Usage with Renderer
|
|
527
|
+
|
|
528
|
+
```javascript
|
|
529
|
+
<PluginModule
|
|
530
|
+
component={Component.MasterpassCardRegistration}
|
|
531
|
+
props={{
|
|
532
|
+
// Do not remove getValues, it is used to get the form values
|
|
533
|
+
getValues,
|
|
534
|
+
renderer: {
|
|
535
|
+
InfoModal: ({ open, setOpen }) => (
|
|
536
|
+
<Modal
|
|
537
|
+
open={open}
|
|
538
|
+
setOpen={setOpen}
|
|
539
|
+
portalId="masterpass-info-modal"
|
|
540
|
+
>
|
|
541
|
+
masterpass information
|
|
542
|
+
</Modal>
|
|
543
|
+
),
|
|
544
|
+
Content: ({
|
|
545
|
+
isChecked,
|
|
546
|
+
toggle,
|
|
547
|
+
setIsInfoModalOpen,
|
|
548
|
+
onChange,
|
|
549
|
+
onSubmit,
|
|
550
|
+
loading,
|
|
551
|
+
error,
|
|
552
|
+
showGoBackLink,
|
|
553
|
+
onClickGoBackLink
|
|
554
|
+
}) => (
|
|
555
|
+
<div className="bg-[#F8F8F8] mx-10 mb-5 px-2 py-1">
|
|
556
|
+
<div className="flex items-center gap-2 pb-4 mb-4">
|
|
557
|
+
<Checkbox checked={isChecked} onChange={toggle} />
|
|
558
|
+
<h1>Masterpass</h1>
|
|
559
|
+
<div>
|
|
560
|
+
<Icon
|
|
561
|
+
name="info"
|
|
562
|
+
size={15}
|
|
563
|
+
className="fill-[#000000] cursor-pointer"
|
|
564
|
+
onClick={() => {
|
|
565
|
+
setIsInfoModalOpen(true);
|
|
566
|
+
}}
|
|
567
|
+
/>
|
|
568
|
+
</div>
|
|
569
|
+
</div>
|
|
570
|
+
<p className="text-xs">
|
|
571
|
+
I want to store my card information in the Mastercard infrastructure and use it again in my next purchase.
|
|
572
|
+
</p>
|
|
573
|
+
{isChecked && (
|
|
574
|
+
<>
|
|
575
|
+
<Input
|
|
576
|
+
label="Give your card a name"
|
|
577
|
+
onChange={(e) =>
|
|
578
|
+
onChange((e.target as HTMLInputElement).value)
|
|
579
|
+
}
|
|
580
|
+
className="mt-2"
|
|
581
|
+
/>
|
|
582
|
+
<Button
|
|
583
|
+
onClick={onSubmit}
|
|
584
|
+
className="bg-green-600 text-black w-full mt-3"
|
|
585
|
+
>
|
|
586
|
+
{loading ? 'Loading...' : 'Save'}
|
|
587
|
+
</Button>
|
|
588
|
+
{error && (
|
|
589
|
+
<p className="text-xs text-red-600 mt-2">🚨 {error}</p>
|
|
590
|
+
)}
|
|
591
|
+
</>
|
|
592
|
+
)}
|
|
593
|
+
{showGoBackLink && (
|
|
594
|
+
<div>
|
|
595
|
+
<button
|
|
596
|
+
onClick={onClickGoBackLink}
|
|
597
|
+
className="text-xs text-red-600 underline mt-3"
|
|
598
|
+
>
|
|
599
|
+
I want to pay with my card registered to Masterpass.
|
|
600
|
+
</button>
|
|
601
|
+
</div>
|
|
602
|
+
)}
|
|
603
|
+
</div>
|
|
604
|
+
)
|
|
605
|
+
}
|
|
606
|
+
}}
|
|
607
|
+
```
|
package/package.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akinon/pz-masterpass",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.20",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.d.ts",
|
|
7
7
|
"peerDependencies": {
|
|
8
|
-
"react": "^19.0.0",
|
|
9
|
-
"react-dom": "^19.0.0"
|
|
8
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
9
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
10
10
|
},
|
|
11
11
|
"devDependencies": {
|
|
12
|
-
"@types/node": "^
|
|
13
|
-
"@types/react": "^
|
|
14
|
-
"@types/react-dom": "^
|
|
12
|
+
"@types/node": "^18.7.8",
|
|
13
|
+
"@types/react": "^18.0.17",
|
|
14
|
+
"@types/react-dom": "^18.0.6",
|
|
15
15
|
"@types/jquery": "^3.5.16",
|
|
16
|
-
"typescript": "^
|
|
16
|
+
"typescript": "^4.7.4"
|
|
17
17
|
}
|
|
18
18
|
}
|
package/src/redux/reducer.ts
CHANGED
|
@@ -116,6 +116,19 @@ const rootSlice = createSlice({
|
|
|
116
116
|
},
|
|
117
117
|
setCvcRequired: (state, { payload }: PayloadAction<boolean>) => {
|
|
118
118
|
state.cvcRequired = payload;
|
|
119
|
+
},
|
|
120
|
+
resetMasterpassState: (state) => {
|
|
121
|
+
state.cards = [];
|
|
122
|
+
state.otp = {
|
|
123
|
+
isModalVisible: false
|
|
124
|
+
};
|
|
125
|
+
state.isDirectPurchase = true;
|
|
126
|
+
state.deletion = {
|
|
127
|
+
isModalVisible: false
|
|
128
|
+
};
|
|
129
|
+
state.additionalParams = {};
|
|
130
|
+
state.cvcRequired = false;
|
|
131
|
+
state.selectedCard = undefined;
|
|
119
132
|
}
|
|
120
133
|
}
|
|
121
134
|
});
|
|
@@ -137,7 +150,8 @@ export const {
|
|
|
137
150
|
setDeletionModalVisible,
|
|
138
151
|
setDeletionCardAliasName,
|
|
139
152
|
setAdditionalParams,
|
|
140
|
-
setCvcRequired
|
|
153
|
+
setCvcRequired,
|
|
154
|
+
resetMasterpassState
|
|
141
155
|
} = rootSlice.actions;
|
|
142
156
|
|
|
143
157
|
export default rootSlice.reducer;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import { JSX } from 'react';
|
|
3
4
|
import { LoaderSpinner } from 'components';
|
|
4
5
|
import { MasterpassStatus } from '../../types';
|
|
5
6
|
import { getCreditCardType } from '../../utils';
|
|
@@ -48,6 +49,25 @@ const defaultTranslations = {
|
|
|
48
49
|
security_code_info: 'What’s CVC?'
|
|
49
50
|
};
|
|
50
51
|
|
|
52
|
+
interface RendererProps {
|
|
53
|
+
Header?: () => JSX.Element;
|
|
54
|
+
CardItem?: (params: {
|
|
55
|
+
card: any;
|
|
56
|
+
selectedCard: any;
|
|
57
|
+
onSelect: (card: any) => Promise<void>;
|
|
58
|
+
DeleteButton?: React.FC<{ cardAliasName: string; onDelete: () => void }>;
|
|
59
|
+
}) => JSX.Element;
|
|
60
|
+
Loader?: () => JSX.Element;
|
|
61
|
+
ErrorFallback?: (params: {
|
|
62
|
+
error: string;
|
|
63
|
+
onRetry: () => void;
|
|
64
|
+
}) => JSX.Element;
|
|
65
|
+
SwitchPaymentButton?: (props: {
|
|
66
|
+
onClick: () => void;
|
|
67
|
+
label: string;
|
|
68
|
+
}) => JSX.Element;
|
|
69
|
+
}
|
|
70
|
+
|
|
51
71
|
export interface MasterpassCardListProps {
|
|
52
72
|
className?: string;
|
|
53
73
|
translations?: typeof defaultTranslations;
|
|
@@ -58,12 +78,14 @@ export interface MasterpassCardListProps {
|
|
|
58
78
|
register: UseFormRegister<any>;
|
|
59
79
|
setFormValue: UseFormSetValue<any>;
|
|
60
80
|
};
|
|
81
|
+
renderer?: RendererProps;
|
|
61
82
|
}
|
|
62
83
|
|
|
63
84
|
export const MasterpassCardList = ({
|
|
64
85
|
className,
|
|
65
86
|
translations,
|
|
66
|
-
form
|
|
87
|
+
form,
|
|
88
|
+
renderer
|
|
67
89
|
}: MasterpassCardListProps) => {
|
|
68
90
|
const { preOrder } = useAppSelector((state) => state.checkout);
|
|
69
91
|
const { accountStatus, isDirectPurchase, selectedCard, cvcRequired } =
|
|
@@ -73,7 +95,34 @@ export const MasterpassCardList = ({
|
|
|
73
95
|
const { DeleteButton } = useDeleteCard();
|
|
74
96
|
const dispatch = useAppDispatch();
|
|
75
97
|
|
|
76
|
-
const
|
|
98
|
+
const onSelectCard = async (card: any) => {
|
|
99
|
+
if (selectedCard?.UniqueId !== card.UniqueId) {
|
|
100
|
+
const maskStartIndex = card.Value1.indexOf('*');
|
|
101
|
+
const binNumber =
|
|
102
|
+
maskStartIndex !== -1
|
|
103
|
+
? card.Value1.substring(0, maskStartIndex)
|
|
104
|
+
: card.Value1.substring(0, 6);
|
|
105
|
+
|
|
106
|
+
await setMasterpassBinNumber(binNumber).unwrap();
|
|
107
|
+
dispatch(setSelectedCard(card));
|
|
108
|
+
dispatch(setCvcRequired(false));
|
|
109
|
+
if (form) {
|
|
110
|
+
form.setFormValue('card_cvv', '');
|
|
111
|
+
form.clearErrors('card_cvv');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const switchPaymentButton = renderer?.SwitchPaymentButton ? (
|
|
117
|
+
renderer.SwitchPaymentButton({
|
|
118
|
+
onClick: () => {
|
|
119
|
+
dispatch(setIsDirectPurchase(true));
|
|
120
|
+
dispatch(setInstallmentOptions([]));
|
|
121
|
+
},
|
|
122
|
+
label:
|
|
123
|
+
translations?.pay_with_new_card ?? defaultTranslations.pay_with_new_card
|
|
124
|
+
})
|
|
125
|
+
) : (
|
|
77
126
|
<a
|
|
78
127
|
href="#"
|
|
79
128
|
className="text-xs underline"
|
|
@@ -92,7 +141,9 @@ export const MasterpassCardList = ({
|
|
|
92
141
|
}
|
|
93
142
|
|
|
94
143
|
if (loading) {
|
|
95
|
-
return (
|
|
144
|
+
return renderer?.Loader ? (
|
|
145
|
+
renderer.Loader()
|
|
146
|
+
) : (
|
|
96
147
|
<div className="p-5">
|
|
97
148
|
<LoaderSpinner className="w-8 h-8" />
|
|
98
149
|
</div>
|
|
@@ -100,18 +151,14 @@ export const MasterpassCardList = ({
|
|
|
100
151
|
}
|
|
101
152
|
|
|
102
153
|
if (error && !isDirectPurchase) {
|
|
103
|
-
return (
|
|
154
|
+
return renderer?.ErrorFallback ? (
|
|
155
|
+
renderer.ErrorFallback({ error, onRetry: refreshCards })
|
|
156
|
+
) : (
|
|
104
157
|
<div className="flex flex-col items-center">
|
|
105
158
|
<div className="p-5">{error}</div>
|
|
106
|
-
<Button
|
|
107
|
-
className="w-48 mb-5"
|
|
108
|
-
onClick={() => {
|
|
109
|
-
refreshCards();
|
|
110
|
-
}}
|
|
111
|
-
>
|
|
159
|
+
<Button className="w-48 mb-5" onClick={refreshCards}>
|
|
112
160
|
{translations?.retryFetchCards ?? defaultTranslations.retryFetchCards}
|
|
113
161
|
</Button>
|
|
114
|
-
|
|
115
162
|
{switchPaymentButton}
|
|
116
163
|
</div>
|
|
117
164
|
);
|
|
@@ -126,38 +173,75 @@ export const MasterpassCardList = ({
|
|
|
126
173
|
}
|
|
127
174
|
|
|
128
175
|
return (
|
|
129
|
-
<div className=
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
176
|
+
<div className="w-full">
|
|
177
|
+
{renderer?.Header ? (
|
|
178
|
+
renderer.Header()
|
|
179
|
+
) : (
|
|
180
|
+
<>
|
|
181
|
+
<Image
|
|
182
|
+
className="mb-4"
|
|
183
|
+
width={140}
|
|
184
|
+
height={25}
|
|
185
|
+
src={masterpassLogo.src}
|
|
186
|
+
alt="Masterpass Logo"
|
|
187
|
+
/>
|
|
188
|
+
<p className="text-xs">
|
|
189
|
+
{translations?.title ?? defaultTranslations.title}
|
|
190
|
+
</p>
|
|
191
|
+
</>
|
|
192
|
+
)}
|
|
193
|
+
|
|
194
|
+
<ul className={twMerge('mt-4 text-xs', className)}>
|
|
141
195
|
{cards?.map((card) => {
|
|
142
|
-
|
|
196
|
+
const cvcField =
|
|
197
|
+
selectedCard?.UniqueId === card.UniqueId && form && cvcRequired ? (
|
|
198
|
+
<div
|
|
199
|
+
className={twMerge(
|
|
200
|
+
clsx('flex items-center justify-start mt-2', {
|
|
201
|
+
'items-baseline': form.errors.card_cvv
|
|
202
|
+
})
|
|
203
|
+
)}
|
|
204
|
+
>
|
|
205
|
+
<label
|
|
206
|
+
className="text-xs text-black-400 mr-1.5"
|
|
207
|
+
htmlFor="card_cvv"
|
|
208
|
+
>
|
|
209
|
+
{translations?.security_code ??
|
|
210
|
+
defaultTranslations.security_code}
|
|
211
|
+
</label>
|
|
212
|
+
<Input
|
|
213
|
+
format="###"
|
|
214
|
+
mask="_"
|
|
215
|
+
control={form.control}
|
|
216
|
+
allowEmptyFormatting={true}
|
|
217
|
+
{...form.register('card_cvv')}
|
|
218
|
+
error={form.errors.card_cvv}
|
|
219
|
+
/>
|
|
220
|
+
<div className="group relative flex items-center justify-start text-gray-600 cursor-pointer ml-2 transition-all hover:text-secondary">
|
|
221
|
+
<span className="text-xs underline">
|
|
222
|
+
{translations?.security_code_info ??
|
|
223
|
+
defaultTranslations.security_code_info}
|
|
224
|
+
</span>
|
|
225
|
+
<Icon name="cvc" size={16} className="leading-none ml-2" />
|
|
226
|
+
<div className="hidden group-hover:block absolute right-0 bottom-5 w-[11rem] lg:w-[21rem] lg:left-auto lg:right-auto border-2">
|
|
227
|
+
<Image src="/cvv.jpg" alt="Cvv" width={385} height={262} />
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
) : null;
|
|
232
|
+
|
|
233
|
+
return renderer?.CardItem ? (
|
|
234
|
+
renderer.CardItem({
|
|
235
|
+
card,
|
|
236
|
+
selectedCard,
|
|
237
|
+
onSelect: onSelectCard,
|
|
238
|
+
DeleteButton
|
|
239
|
+
})
|
|
240
|
+
) : (
|
|
143
241
|
<li
|
|
144
242
|
key={card.UniqueId}
|
|
145
243
|
className="p-4 mb-2 border-2 border-gray-200 flex flex-col space-x-2 cursor-pointer"
|
|
146
|
-
onClick={
|
|
147
|
-
if (selectedCard?.UniqueId !== card.UniqueId) {
|
|
148
|
-
await setMasterpassBinNumber(
|
|
149
|
-
card.Value1.substring(0, 6)
|
|
150
|
-
).unwrap();
|
|
151
|
-
|
|
152
|
-
dispatch(setSelectedCard(card));
|
|
153
|
-
dispatch(setCvcRequired(false));
|
|
154
|
-
|
|
155
|
-
if (form) {
|
|
156
|
-
form.setFormValue('card_cvv', '');
|
|
157
|
-
form.clearErrors('card_cvv');
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}}
|
|
244
|
+
onClick={() => onSelectCard(card)}
|
|
161
245
|
>
|
|
162
246
|
<div className="flex justify-between items-center">
|
|
163
247
|
<input
|
|
@@ -183,49 +267,9 @@ export const MasterpassCardList = ({
|
|
|
183
267
|
alt={card.Name}
|
|
184
268
|
/>
|
|
185
269
|
</label>
|
|
186
|
-
|
|
187
270
|
<DeleteButton cardAliasName={card.Name} />
|
|
188
271
|
</div>
|
|
189
|
-
{
|
|
190
|
-
<div
|
|
191
|
-
className={twMerge(
|
|
192
|
-
clsx('flex items-center justify-start mt-2', {
|
|
193
|
-
'items-baseline': form.errors.card_cvv
|
|
194
|
-
})
|
|
195
|
-
)}
|
|
196
|
-
>
|
|
197
|
-
<label
|
|
198
|
-
className="text-xs text-black-400 mr-1.5"
|
|
199
|
-
htmlFor="card_cvv"
|
|
200
|
-
>
|
|
201
|
-
{translations?.security_code ??
|
|
202
|
-
defaultTranslations.security_code}
|
|
203
|
-
</label>
|
|
204
|
-
<Input
|
|
205
|
-
format="###"
|
|
206
|
-
mask="_"
|
|
207
|
-
control={form.control}
|
|
208
|
-
allowEmptyFormatting={true}
|
|
209
|
-
{...form.register('card_cvv')}
|
|
210
|
-
error={form.errors.card_cvv}
|
|
211
|
-
/>
|
|
212
|
-
<div className="group relative flex items-center justify-start text-gray-600 cursor-pointer ml-2 transition-all hover:text-secondary">
|
|
213
|
-
<span className="text-xs underline">
|
|
214
|
-
{translations?.security_code_info ??
|
|
215
|
-
defaultTranslations.security_code_info}
|
|
216
|
-
</span>
|
|
217
|
-
<Icon name="cvc" size={16} className="leading-none ml-2" />
|
|
218
|
-
<div className="hidden group-hover:block absolute right-0 bottom-5 w-[11rem] lg:w-[21rem] lg:left-auto lg:right-auto border-2">
|
|
219
|
-
<Image
|
|
220
|
-
src="/cvv.jpg"
|
|
221
|
-
alt="Cvv"
|
|
222
|
-
width={385}
|
|
223
|
-
height={262}
|
|
224
|
-
/>
|
|
225
|
-
</div>
|
|
226
|
-
</div>
|
|
227
|
-
</div>
|
|
228
|
-
)}
|
|
272
|
+
{cvcField}
|
|
229
273
|
</li>
|
|
230
274
|
);
|
|
231
275
|
})}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useAppDispatch, useAppSelector } from '@akinon/next/redux/hooks';
|
|
2
2
|
import { Button, Checkbox, Icon, Input, LoaderSpinner } from 'components';
|
|
3
|
-
import React, { useEffect, useState } from 'react';
|
|
3
|
+
import React, { JSX, useEffect, useState } from 'react';
|
|
4
4
|
import { RootState } from 'redux/store';
|
|
5
5
|
import masterpassLogo from '../../../assets/img/mp_masterpass-logo.png';
|
|
6
6
|
import { formCreator } from '../../utils';
|
|
@@ -32,13 +32,31 @@ export const MasterpassCardRegistration = ({
|
|
|
32
32
|
className,
|
|
33
33
|
infoModalContent,
|
|
34
34
|
infoModalIcon,
|
|
35
|
-
translations
|
|
35
|
+
translations,
|
|
36
|
+
renderer = {}
|
|
36
37
|
}: {
|
|
37
38
|
getValues: () => Record<string, string | number | boolean>;
|
|
38
39
|
className?: string;
|
|
39
40
|
infoModalContent?: React.ReactNode;
|
|
40
41
|
infoModalIcon?: React.ReactNode;
|
|
41
42
|
translations?: typeof defaultTranslations;
|
|
43
|
+
renderer?: {
|
|
44
|
+
Content?: (props: {
|
|
45
|
+
isChecked: boolean;
|
|
46
|
+
toggle: () => void;
|
|
47
|
+
setIsInfoModalOpen: (value: boolean) => void;
|
|
48
|
+
onChange: (value: string) => void;
|
|
49
|
+
onSubmit: () => void;
|
|
50
|
+
loading: boolean;
|
|
51
|
+
error: string | null;
|
|
52
|
+
showGoBackLink?: boolean;
|
|
53
|
+
onClickGoBackLink?: () => void;
|
|
54
|
+
}) => JSX.Element;
|
|
55
|
+
InfoModal?: (props: {
|
|
56
|
+
open: boolean;
|
|
57
|
+
setOpen: (value: boolean) => void;
|
|
58
|
+
}) => JSX.Element;
|
|
59
|
+
};
|
|
42
60
|
}) => {
|
|
43
61
|
const { preOrder } = useAppSelector((state: RootState) => state.checkout);
|
|
44
62
|
const { msisdn, token, language, otp, isDirectPurchase, accountStatus } =
|
|
@@ -168,13 +186,56 @@ export const MasterpassCardRegistration = ({
|
|
|
168
186
|
return null;
|
|
169
187
|
}
|
|
170
188
|
|
|
189
|
+
if (renderer.Content) {
|
|
190
|
+
return (
|
|
191
|
+
<>
|
|
192
|
+
{renderer.InfoModal ? (
|
|
193
|
+
<renderer.InfoModal
|
|
194
|
+
open={isInfoModalOpen}
|
|
195
|
+
setOpen={setIsInfoModalOpen}
|
|
196
|
+
/>
|
|
197
|
+
) : (
|
|
198
|
+
<InfoModal
|
|
199
|
+
open={isInfoModalOpen}
|
|
200
|
+
setOpen={setIsInfoModalOpen}
|
|
201
|
+
content={infoModalContent}
|
|
202
|
+
/>
|
|
203
|
+
)}
|
|
204
|
+
|
|
205
|
+
<renderer.Content
|
|
206
|
+
isChecked={isAgreementChecked}
|
|
207
|
+
toggle={() => setIsAgreementChecked(!isAgreementChecked)}
|
|
208
|
+
setIsInfoModalOpen={setIsInfoModalOpen}
|
|
209
|
+
onChange={(value) => setAccountAliasName(value)}
|
|
210
|
+
onSubmit={registerCard}
|
|
211
|
+
loading={isBusy}
|
|
212
|
+
error={formError}
|
|
213
|
+
showGoBackLink={
|
|
214
|
+
accountStatus !== MasterpassStatus.NoAccount && cards?.length > 0
|
|
215
|
+
}
|
|
216
|
+
onClickGoBackLink={() => {
|
|
217
|
+
dispatch(setIsDirectPurchase(false));
|
|
218
|
+
dispatch(setInstallmentOptions([]));
|
|
219
|
+
}}
|
|
220
|
+
/>
|
|
221
|
+
</>
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
171
225
|
return (
|
|
172
226
|
<div className={twMerge('w-full', className)}>
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
227
|
+
{renderer.InfoModal ? (
|
|
228
|
+
<renderer.InfoModal
|
|
229
|
+
open={isInfoModalOpen}
|
|
230
|
+
setOpen={setIsInfoModalOpen}
|
|
231
|
+
/>
|
|
232
|
+
) : (
|
|
233
|
+
<InfoModal
|
|
234
|
+
open={isInfoModalOpen}
|
|
235
|
+
setOpen={setIsInfoModalOpen}
|
|
236
|
+
content={infoModalContent}
|
|
237
|
+
/>
|
|
238
|
+
)}
|
|
178
239
|
|
|
179
240
|
<div className="border border-[#ddd]">
|
|
180
241
|
<div className="p-4">
|
|
@@ -232,7 +293,7 @@ export const MasterpassCardRegistration = ({
|
|
|
232
293
|
defaultTranslations.enter_card_name
|
|
233
294
|
}
|
|
234
295
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
235
|
-
setAccountAliasName(e.target.value);
|
|
296
|
+
setAccountAliasName(e.target.value.trim());
|
|
236
297
|
}}
|
|
237
298
|
/>
|
|
238
299
|
<Button
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import { JSX } from 'react';
|
|
3
4
|
import { Button, LoaderSpinner, Modal, Image } from '@akinon/next/components';
|
|
4
5
|
|
|
5
6
|
import { useAppDispatch, useAppSelector } from '@akinon/next/redux/hooks';
|
|
@@ -15,15 +16,39 @@ const defaultTranslations = {
|
|
|
15
16
|
|
|
16
17
|
export interface MasterpassDeleteConfirmationModalProps {
|
|
17
18
|
translations?: typeof defaultTranslations;
|
|
19
|
+
renderer?: {
|
|
20
|
+
Content?: (props: {
|
|
21
|
+
open: boolean;
|
|
22
|
+
setOpen: (open: boolean) => void;
|
|
23
|
+
onConfirm: () => void;
|
|
24
|
+
onCancel: () => void;
|
|
25
|
+
loading: boolean;
|
|
26
|
+
error: string | null;
|
|
27
|
+
}) => JSX.Element;
|
|
28
|
+
};
|
|
18
29
|
}
|
|
19
30
|
|
|
20
31
|
export const MasterpassDeleteConfirmationModal = ({
|
|
21
|
-
translations
|
|
32
|
+
translations,
|
|
33
|
+
renderer = {}
|
|
22
34
|
}: MasterpassDeleteConfirmationModalProps) => {
|
|
23
35
|
const { deletion } = useAppSelector((state) => state.masterpass);
|
|
24
36
|
const { deleteCard, isLoading, error } = useDeleteCard();
|
|
25
37
|
const dispatch = useAppDispatch();
|
|
26
38
|
|
|
39
|
+
if (renderer.Content) {
|
|
40
|
+
return (
|
|
41
|
+
<renderer.Content
|
|
42
|
+
open={deletion.isModalVisible}
|
|
43
|
+
setOpen={() => dispatch(setDeletionModalVisible(false))}
|
|
44
|
+
onConfirm={deleteCard}
|
|
45
|
+
onCancel={() => dispatch(setDeletionModalVisible(false))}
|
|
46
|
+
loading={isLoading}
|
|
47
|
+
error={error}
|
|
48
|
+
/>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
27
52
|
return (
|
|
28
53
|
<Modal
|
|
29
54
|
portalId="masterpass-remove-card-modal"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useAppDispatch, useAppSelector } from '@akinon/next/redux/hooks';
|
|
4
4
|
import { Button, LoaderSpinner, Modal } from 'components';
|
|
5
|
-
import { useCallback, useEffect, useState } from 'react';
|
|
5
|
+
import { JSX, useCallback, useEffect, useState } from 'react';
|
|
6
6
|
import masterpassLogo from '../../../assets/img/mp_masterpass-logo.png';
|
|
7
7
|
import {
|
|
8
8
|
setAccountStatus,
|
|
@@ -21,10 +21,20 @@ const defaultTranslations = {
|
|
|
21
21
|
|
|
22
22
|
export interface MasterpassLinkModalProps {
|
|
23
23
|
translations?: typeof defaultTranslations;
|
|
24
|
+
renderer?: {
|
|
25
|
+
Content?: (props: {
|
|
26
|
+
open: boolean;
|
|
27
|
+
setOpen: (open: boolean) => void;
|
|
28
|
+
onClick: () => void;
|
|
29
|
+
loading: boolean;
|
|
30
|
+
error: string | null;
|
|
31
|
+
}) => JSX.Element;
|
|
32
|
+
};
|
|
24
33
|
}
|
|
25
34
|
|
|
26
35
|
export const MasterpassLinkModal = ({
|
|
27
|
-
translations
|
|
36
|
+
translations,
|
|
37
|
+
renderer = {}
|
|
28
38
|
}: MasterpassLinkModalProps) => {
|
|
29
39
|
const { msisdn, token, accountStatus, otp, language } = useAppSelector(
|
|
30
40
|
(state) => state.masterpass
|
|
@@ -93,6 +103,18 @@ export const MasterpassLinkModal = ({
|
|
|
93
103
|
}
|
|
94
104
|
}, [otp.response]);
|
|
95
105
|
|
|
106
|
+
if (renderer.Content) {
|
|
107
|
+
return (
|
|
108
|
+
<renderer.Content
|
|
109
|
+
open={isOpen}
|
|
110
|
+
setOpen={setIsOpen}
|
|
111
|
+
onClick={onLinkButtonClick}
|
|
112
|
+
loading={isLoading}
|
|
113
|
+
error={error}
|
|
114
|
+
/>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
96
118
|
return (
|
|
97
119
|
<Modal
|
|
98
120
|
portalId="masterpass-check-user"
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { Modal } from '@akinon/next/components';
|
|
4
4
|
import masterpassLogo from '../../../assets/img/mp_masterpass-logo.png';
|
|
5
5
|
|
|
6
|
-
import { useCallback, useEffect, useState } from 'react';
|
|
6
|
+
import { JSX, useCallback, useEffect, useState } from 'react';
|
|
7
7
|
|
|
8
8
|
import { formCreator } from '../../utils';
|
|
9
9
|
|
|
@@ -17,10 +17,24 @@ export interface MasterpassOtpModalProps {
|
|
|
17
17
|
translations?: {
|
|
18
18
|
5001?: string;
|
|
19
19
|
} & OtpFormProps['translations'];
|
|
20
|
+
renderer?: {
|
|
21
|
+
Content?: (props: {
|
|
22
|
+
open: boolean;
|
|
23
|
+
setOpen: (open: boolean) => void;
|
|
24
|
+
onSubmit: (data: { otp_code: string }) => void;
|
|
25
|
+
loading: boolean;
|
|
26
|
+
error: string | null;
|
|
27
|
+
resendSms: () => void;
|
|
28
|
+
resendSmsFetching: boolean;
|
|
29
|
+
targetDate: number;
|
|
30
|
+
otpRef: string | null;
|
|
31
|
+
}) => JSX.Element;
|
|
32
|
+
};
|
|
20
33
|
}
|
|
21
34
|
|
|
22
35
|
export const MasterpassOtpModal = ({
|
|
23
|
-
translations
|
|
36
|
+
translations,
|
|
37
|
+
renderer = {}
|
|
24
38
|
}: MasterpassOtpModalProps) => {
|
|
25
39
|
const { token, otp, language } = useAppSelector((state) => state.masterpass);
|
|
26
40
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
@@ -33,7 +47,7 @@ export const MasterpassOtpModal = ({
|
|
|
33
47
|
|
|
34
48
|
const onFormSubmit = useCallback(
|
|
35
49
|
(data: { otp_code: string }) => {
|
|
36
|
-
if (!token || !language) {
|
|
50
|
+
if (!token || !language || isBusy) {
|
|
37
51
|
return;
|
|
38
52
|
}
|
|
39
53
|
|
|
@@ -78,7 +92,7 @@ export const MasterpassOtpModal = ({
|
|
|
78
92
|
const resendSms = () => {
|
|
79
93
|
const token = window.MFS.getLastToken();
|
|
80
94
|
|
|
81
|
-
if (!token
|
|
95
|
+
if (!token || isBusy) {
|
|
82
96
|
return;
|
|
83
97
|
}
|
|
84
98
|
|
|
@@ -111,6 +125,22 @@ export const MasterpassOtpModal = ({
|
|
|
111
125
|
}
|
|
112
126
|
}, [isModalOpen]);
|
|
113
127
|
|
|
128
|
+
if (renderer.Content) {
|
|
129
|
+
return (
|
|
130
|
+
<renderer.Content
|
|
131
|
+
open={isModalOpen}
|
|
132
|
+
setOpen={handleModalVisibility}
|
|
133
|
+
onSubmit={onFormSubmit}
|
|
134
|
+
loading={isBusy}
|
|
135
|
+
error={otpError}
|
|
136
|
+
resendSms={resendSms}
|
|
137
|
+
resendSmsFetching={isBusy}
|
|
138
|
+
targetDate={otpTime}
|
|
139
|
+
otpRef={otpRef}
|
|
140
|
+
/>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
114
144
|
return (
|
|
115
145
|
<Modal
|
|
116
146
|
portalId="otp-masterpass"
|
|
@@ -51,7 +51,7 @@ export const OtpForm = ({
|
|
|
51
51
|
reset,
|
|
52
52
|
formState: { errors }
|
|
53
53
|
} = useForm({
|
|
54
|
-
resolver: yupResolver(formSchema())
|
|
54
|
+
resolver: yupResolver(formSchema()) as any
|
|
55
55
|
});
|
|
56
56
|
|
|
57
57
|
useEffect(() => {
|
|
@@ -68,7 +68,7 @@ export const OtpForm = ({
|
|
|
68
68
|
max="999999"
|
|
69
69
|
min="000000"
|
|
70
70
|
label={translations?.sms_code ?? defaultTranslations.sms_code}
|
|
71
|
-
control={control}
|
|
71
|
+
control={control as any}
|
|
72
72
|
{...register('otp_code')}
|
|
73
73
|
error={errors.otp_code}
|
|
74
74
|
/>
|