@akinon/pz-masterpass-rest 2.0.16-rc.0 → 2.0.16

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.
@@ -1,6 +1,6 @@
1
1
  import { ReactElement } from 'react';
2
2
  import type { CreditCardFormData } from '../utils/validation-schemas';
3
- import { CardType, Installment, RewardItem } from './payment.types';
3
+ import { CardType, Installment } from './payment.types';
4
4
 
5
5
  export type PaymentMethodSelectorProps = {
6
6
  cards: any[];
@@ -18,14 +18,6 @@ export type CardListProps = {
18
18
  cvc?: string;
19
19
  onCvcChange?: (cvc: string) => void;
20
20
  cvcRequired?: boolean;
21
- availableRewards?: RewardItem[];
22
- selectedRewards?: RewardItem[];
23
- isLoadingRewards?: boolean;
24
- isConfirmingRewards?: boolean;
25
- onOpenRewardModal?: () => void;
26
- onConfirmRewards?: (selected: RewardItem[]) => Promise<void> | void;
27
- rewardCurrency?: string;
28
- rewardPayableAmount?: string | number | null;
29
21
  texts: MasterpassRestOptionTexts;
30
22
  };
31
23
 
@@ -66,18 +58,6 @@ export type OTPModalProps = {
66
58
  texts: MasterpassRestOptionTexts;
67
59
  };
68
60
 
69
- export type RewardSelectionModalProps = {
70
- open: boolean;
71
- onClose: () => void;
72
- onConfirm: (selected: RewardItem[]) => Promise<void> | void;
73
- rewards: RewardItem[];
74
- selectedRewards: RewardItem[];
75
- isLoading?: boolean;
76
- currency?: string;
77
- payableAmount?: string | number | null;
78
- texts: MasterpassRestOptionTexts;
79
- };
80
-
81
61
  export type ConfirmationModalProps = {
82
62
  open: boolean;
83
63
  onClose: () => void;
@@ -139,14 +119,6 @@ export type MasterpassRestOptionRenderProps = {
139
119
  isInstallmentLoading: boolean;
140
120
  isPrepareLoading: boolean;
141
121
  isFinalizeLoading: boolean;
142
- isProcessingPayment: boolean;
143
- isRewardsQueryLoading: boolean;
144
- isRewardsSelectLoading: boolean;
145
-
146
- openRewardModal: () => void;
147
- closeRewardModal: () => void;
148
- handleConfirmRewards: (selected: RewardItem[]) => Promise<void>;
149
- payableAmount: string | null;
150
122
 
151
123
  texts: MasterpassRestOptionTexts;
152
124
 
@@ -158,7 +130,6 @@ export type MasterpassRestOptionRenderProps = {
158
130
  LinkModal: React.ComponentType<LinkModalProps>;
159
131
  OTPModal: React.ComponentType<OTPModalProps>;
160
132
  ConfirmationModal: React.ComponentType<ConfirmationModalProps>;
161
- RewardSelectionModal: React.ComponentType<RewardSelectionModalProps>;
162
133
  };
163
134
  };
164
135
 
@@ -170,7 +141,6 @@ export type MasterpassRestOptionCustomRender = {
170
141
  linkModal?: (props: LinkModalProps) => ReactElement;
171
142
  otpModal?: (props: OTPModalProps) => ReactElement;
172
143
  confirmationModal?: (props: ConfirmationModalProps) => ReactElement;
173
- rewardSelectionModal?: (props: RewardSelectionModalProps) => ReactElement;
174
144
  errorDisplay?: (props: ErrorDisplayProps) => ReactElement;
175
145
  loadingState?: (props: LoadingStateProps) => ReactElement;
176
146
  emptyState?: (props: EmptyStateProps) => ReactElement;
@@ -307,20 +277,4 @@ export type MasterpassRestOptionTexts = {
307
277
  sessionExpiredMessage?: string;
308
278
  sessionExpiredSecondaryMessage?: string;
309
279
  sessionExpiredButton?: string;
310
-
311
- rewardModalTitle?: string;
312
- rewardModalDescription?: string;
313
- rewardModalEmptyMessage?: string;
314
- rewardModalConfirmText?: string;
315
- rewardModalCancelText?: string;
316
- rewardModalLoadingText?: string;
317
- rewardOpenButtonText?: string;
318
- rewardSelectedSummaryText?: string;
319
- rewardAvailableSummaryText?: string;
320
- rewardClearText?: string;
321
- rewardCategorySpecialText?: string;
322
- rewardCategoryGeneralText?: string;
323
- rewardFailedToLoadText?: string;
324
- rewardFailedToSelectText?: string;
325
- rewardCappedNoticeText?: string;
326
280
  };
@@ -69,15 +69,6 @@ export type TransactionType =
69
69
  | 'DIRECT_PURCHASE'
70
70
  | 'DIRECT_PURCHASE_3D';
71
71
 
72
- export type RewardName = 'FBB' | 'BNS';
73
- export type RewardCategory = 'special' | 'general';
74
-
75
- export interface RewardItem {
76
- type: RewardCategory;
77
- amount: number | string;
78
- name?: RewardName;
79
- }
80
-
81
72
  export interface PaymentState {
82
73
  selectedCard: any | null;
83
74
  selectedInstallment: Installment | null;
@@ -87,9 +78,6 @@ export interface PaymentState {
87
78
  orderCompleted: boolean;
88
79
  cvc: string;
89
80
  useThreeD: boolean;
90
- availableRewards: RewardItem[];
91
- selectedRewards: RewardItem[];
92
- isLoadingRewards: boolean;
93
81
  }
94
82
 
95
83
  export type InformationModalType = 'warning' | 'error' | 'success' | 'info';
@@ -107,7 +95,6 @@ export interface ModalState {
107
95
  showOTPModal: boolean;
108
96
  show3DSecureModal: boolean;
109
97
  showInformationModal: boolean;
110
- showRewardModal: boolean;
111
98
  informationModalData: InformationModalData | null;
112
99
  otpType: OTPType;
113
100
  verificationData: any | null;
@@ -9,7 +9,7 @@ export const formatCardNumber = (value: string): string => {
9
9
 
10
10
  const formatted = cleanValue.replace(/(\d{4})(?=\d)/g, '$1 ');
11
11
 
12
- return formatted.substring(0, 19);
12
+ return formatted.substring(0, 23);
13
13
  };
14
14
 
15
15
  export const formatExpiryDate = (value: string): string => {
@@ -29,8 +29,6 @@ import InstallmentList from '../components/installment-list';
29
29
  import CreditCardForm from '../components/credit-card-form';
30
30
  import PaymentMethodSelector from '../components/payment-method-selector';
31
31
  import InformationModal from '../components/information-modal';
32
- import RewardSelectionModal from '../components/reward-selection-modal';
33
- import type { RewardItem } from '../types/payment.types';
34
32
  import mpBlackLogo from '../assets/img/masterpass-black-logo.png';
35
33
 
36
34
  interface MasterpassRestOptionProps {
@@ -40,7 +38,6 @@ interface MasterpassRestOptionProps {
40
38
  environment?: MasterpassEnvironment;
41
39
  texts?: MasterpassRestOptionTexts;
42
40
  customRender?: MasterpassRestOptionCustomRender;
43
- enableRewards?: boolean;
44
41
  }
45
42
 
46
43
  const defaultTexts: MasterpassRestOptionTexts = {
@@ -161,23 +158,7 @@ const defaultTexts: MasterpassRestOptionTexts = {
161
158
  sessionExpiredTitle: 'Session Expired',
162
159
  sessionExpiredMessage: 'Your session has expired due to inactivity. Please restart the process to continue.',
163
160
  sessionExpiredSecondaryMessage: 'For security reasons, sessions are only valid for a limited time.',
164
- sessionExpiredButton: 'Start Again',
165
-
166
- rewardModalTitle: 'Use Card Rewards',
167
- rewardModalDescription: 'Select the rewards you want to apply to this order.',
168
- rewardModalEmptyMessage: 'No rewards available for this card.',
169
- rewardModalConfirmText: 'Apply',
170
- rewardModalCancelText: 'Cancel',
171
- rewardModalLoadingText: 'Applying...',
172
- rewardOpenButtonText: 'Use Rewards',
173
- rewardSelectedSummaryText: '{count} reward(s) applied',
174
- rewardAvailableSummaryText: '{count} reward(s) available',
175
- rewardClearText: 'Clear',
176
- rewardCategorySpecialText: 'Special Rewards',
177
- rewardCategoryGeneralText: 'General Rewards',
178
- rewardFailedToLoadText: 'Failed to load rewards',
179
- rewardFailedToSelectText: 'Failed to apply rewards',
180
- rewardCappedNoticeText: '{amount} will be applied'
161
+ sessionExpiredButton: 'Start Again'
181
162
  };
182
163
 
183
164
  const mergeTexts = (
@@ -196,8 +177,7 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
196
177
  sdkUrl,
197
178
  environment,
198
179
  texts = defaultTexts,
199
- customRender,
200
- enableRewards = false
180
+ customRender
201
181
  }) => {
202
182
  const mergedTexts = React.useMemo(
203
183
  () => mergeTexts(texts, defaultTexts),
@@ -221,7 +201,6 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
221
201
  'stored_card' | 'new_card'
222
202
  >('stored_card');
223
203
  const [cvc, setCvc] = useState<string>('');
224
- const [isProcessingPayment, setIsProcessingPayment] = useState(false);
225
204
 
226
205
  const [setMasterpassRestBinNumber] = useSetMasterpassRestBinNumberMutation();
227
206
 
@@ -230,18 +209,12 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
230
209
  isInstallmentLoading,
231
210
  isPrepareLoading,
232
211
  isFinalizeLoading,
233
- isRewardsQueryLoading,
234
- isRewardsSelectLoading,
235
212
  updatePaymentState,
236
213
  handleCardSelect,
237
214
  handleInstallmentSelect,
238
215
  processPayment,
239
- processDirectPayment,
240
- openRewardModal,
241
- closeRewardModal,
242
- confirmRewards,
243
- payableAmount
244
- } = useMasterpassPayment({ enableRewards });
216
+ processDirectPayment
217
+ } = useMasterpassPayment();
245
218
 
246
219
  const {
247
220
  accountData,
@@ -409,10 +382,6 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
409
382
  }, [dispatch, handleAddCard, mergedTexts.failedToSaveCardText]);
410
383
 
411
384
  const handleProceedToPayment = useCallback(async () => {
412
- if (isProcessingPayment) return;
413
-
414
- setIsProcessingPayment(true);
415
-
416
385
  try {
417
386
  dispatch(clearAllErrors());
418
387
 
@@ -439,10 +408,8 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
439
408
  const errorMessage =
440
409
  error instanceof Error ? error.message : mergedTexts.paymentFailedText;
441
410
  dispatch(setError(errorMessage));
442
- } finally {
443
- setIsProcessingPayment(false);
444
411
  }
445
- }, [isProcessingPayment, dispatch, paymentMethod, processDirectPayment, processPayment, updateModalState, mergedTexts.paymentFailedText]);
412
+ }, [dispatch, paymentMethod, processDirectPayment, processPayment, updateModalState, mergedTexts.paymentFailedText]);
446
413
 
447
414
  const handleOTPSubmitWithErrorHandling = useCallback(async (otp: string) => {
448
415
  if (!handleOTPSubmit)
@@ -482,18 +449,6 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
482
449
  }
483
450
  }, [dispatch, handleInstallmentSelect, mergedTexts.failedToSelectInstallmentText]);
484
451
 
485
- const handleConfirmRewards = useCallback(
486
- async (selected: RewardItem[]) => {
487
- const result = await confirmRewards(selected);
488
- if (!result.success) {
489
- dispatch(
490
- setError(result.message || mergedTexts.rewardFailedToSelectText)
491
- );
492
- }
493
- },
494
- [confirmRewards, dispatch, mergedTexts.rewardFailedToSelectText]
495
- );
496
-
497
452
  const handleCvcChange = useCallback((newCvc: string) => {
498
453
  setCvc(newCvc);
499
454
  updatePaymentState({ cvc: newCvc });
@@ -531,8 +486,7 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
531
486
  InstallmentList: wrapRenderFunction(customRenderRef.current?.installmentList) || InstallmentList,
532
487
  LinkModal: wrapRenderFunction(customRenderRef.current?.linkModal) || LinkModal,
533
488
  OTPModal: wrapRenderFunction(customRenderRef.current?.otpModal) || OTPModal,
534
- ConfirmationModal: wrapRenderFunction(customRenderRef.current?.confirmationModal) || ConfirmationModal,
535
- RewardSelectionModal: wrapRenderFunction(customRenderRef.current?.rewardSelectionModal) || RewardSelectionModal
489
+ ConfirmationModal: wrapRenderFunction(customRenderRef.current?.confirmationModal) || ConfirmationModal
536
490
  };
537
491
  }, [
538
492
  customRender?.paymentMethodSelector,
@@ -541,8 +495,7 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
541
495
  customRender?.installmentList,
542
496
  customRender?.linkModal,
543
497
  customRender?.otpModal,
544
- customRender?.confirmationModal,
545
- customRender?.rewardSelectionModal
498
+ customRender?.confirmationModal
546
499
  ]);
547
500
 
548
501
  const hasStoredCards =
@@ -588,14 +541,6 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
588
541
  isInstallmentLoading,
589
542
  isPrepareLoading,
590
543
  isFinalizeLoading,
591
- isProcessingPayment,
592
- isRewardsQueryLoading,
593
- isRewardsSelectLoading,
594
-
595
- openRewardModal,
596
- closeRewardModal,
597
- handleConfirmRewards,
598
- payableAmount,
599
544
 
600
545
  texts: mergedTexts,
601
546
 
@@ -628,13 +573,6 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
628
573
  isInstallmentLoading,
629
574
  isPrepareLoading,
630
575
  isFinalizeLoading,
631
- isProcessingPayment,
632
- isRewardsQueryLoading,
633
- isRewardsSelectLoading,
634
- openRewardModal,
635
- closeRewardModal,
636
- handleConfirmRewards,
637
- payableAmount,
638
576
  mergedTexts,
639
577
  memoizedComponents
640
578
  ]);
@@ -713,20 +651,6 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
713
651
  removingCardId: modalState?.removingCardId,
714
652
  cvc: cvc,
715
653
  onCvcChange: handleCvcChange,
716
- ...(enableRewards
717
- ? {
718
- availableRewards: paymentState.availableRewards,
719
- selectedRewards: paymentState.selectedRewards,
720
- isLoadingRewards:
721
- paymentState.isLoadingRewards ||
722
- isRewardsQueryLoading,
723
- isConfirmingRewards: isRewardsSelectLoading,
724
- onOpenRewardModal: openRewardModal,
725
- onConfirmRewards: handleConfirmRewards,
726
- rewardCurrency: stateCurrency || currency,
727
- rewardPayableAmount: payableAmount
728
- }
729
- : {}),
730
654
  texts: mergedTexts
731
655
  })
732
656
  ) : (
@@ -738,20 +662,6 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
738
662
  removingCardId={modalState?.removingCardId}
739
663
  cvc={cvc}
740
664
  onCvcChange={handleCvcChange}
741
- {...(enableRewards
742
- ? {
743
- availableRewards: paymentState.availableRewards,
744
- selectedRewards: paymentState.selectedRewards,
745
- isLoadingRewards:
746
- paymentState.isLoadingRewards ||
747
- isRewardsQueryLoading,
748
- isConfirmingRewards: isRewardsSelectLoading,
749
- onOpenRewardModal: openRewardModal,
750
- onConfirmRewards: handleConfirmRewards,
751
- rewardCurrency: stateCurrency || currency,
752
- rewardPayableAmount: payableAmount
753
- }
754
- : {})}
755
665
  texts={mergedTexts}
756
666
  />
757
667
  )}
@@ -797,10 +707,7 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
797
707
  selectedInstallment: paymentState.selectedInstallment,
798
708
  isLoading: isInstallmentLoading || isPrepareLoading,
799
709
  onProceedToPayment: handleProceedToPayment,
800
- paymentLoading:
801
- isProcessingPayment ||
802
- isPrepareLoading ||
803
- isFinalizeLoading,
710
+ paymentLoading: isFinalizeLoading,
804
711
  texts: mergedTexts
805
712
  })
806
713
  ) : (
@@ -811,11 +718,7 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
811
718
  selectedInstallment={paymentState.selectedInstallment}
812
719
  isLoading={isInstallmentLoading || isPrepareLoading}
813
720
  onProceedToPayment={handleProceedToPayment}
814
- paymentLoading={
815
- isProcessingPayment ||
816
- isPrepareLoading ||
817
- isFinalizeLoading
818
- }
721
+ paymentLoading={isFinalizeLoading}
819
722
  texts={mergedTexts}
820
723
  />
821
724
  )
@@ -991,33 +894,6 @@ const MasterpassRestOption: React.FC<MasterpassRestOptionProps> = ({
991
894
  onClose={() => updateModalState({ showInformationModal: false, informationModalData: null })}
992
895
  data={modalState?.informationModalData}
993
896
  />
994
-
995
- {enableRewards &&
996
- (customRender?.rewardSelectionModal ? (
997
- customRender.rewardSelectionModal({
998
- open: modalState?.showRewardModal ?? false,
999
- onClose: closeRewardModal,
1000
- onConfirm: handleConfirmRewards,
1001
- rewards: paymentState.availableRewards,
1002
- selectedRewards: paymentState.selectedRewards,
1003
- isLoading: isRewardsSelectLoading,
1004
- currency: stateCurrency || currency,
1005
- payableAmount,
1006
- texts: mergedTexts
1007
- })
1008
- ) : (
1009
- <RewardSelectionModal
1010
- open={modalState?.showRewardModal ?? false}
1011
- onClose={closeRewardModal}
1012
- onConfirm={handleConfirmRewards}
1013
- rewards={paymentState.availableRewards}
1014
- selectedRewards={paymentState.selectedRewards}
1015
- isLoading={isRewardsSelectLoading}
1016
- currency={stateCurrency || currency}
1017
- payableAmount={payableAmount}
1018
- texts={mergedTexts}
1019
- />
1020
- ))}
1021
897
  </div>
1022
898
  );
1023
899
  };
@@ -1,194 +0,0 @@
1
- import React, { useEffect, useMemo, useState } from 'react';
2
- import { Modal, Button } from '@akinon/next/components';
3
- import { RewardSelectionModalProps } from '../types/custom-render.types';
4
- import type { RewardItem, RewardCategory } from '../types/payment.types';
5
- import { parseRewardAmount, getCappedRewardAmounts } from '../utils/reward-utils';
6
-
7
- const formatAmount = (amount: number | string, currency?: string) => {
8
- const formatted = parseRewardAmount(amount).toFixed(2);
9
- return currency ? `${formatted} ${currency.toUpperCase()}` : formatted;
10
- };
11
-
12
- const isSameReward = (a: RewardItem, b: RewardItem) => a.type === b.type;
13
-
14
- const RewardSelectionModal: React.FC<RewardSelectionModalProps> = ({
15
- open,
16
- onClose,
17
- onConfirm,
18
- rewards,
19
- selectedRewards,
20
- isLoading = false,
21
- currency,
22
- payableAmount,
23
- texts
24
- }) => {
25
- const [draft, setDraft] = useState<RewardItem[]>(selectedRewards);
26
-
27
- useEffect(() => {
28
- if (open) {
29
- setDraft(selectedRewards);
30
- }
31
- }, [open, selectedRewards]);
32
-
33
- const cappedAmounts = useMemo(
34
- () => getCappedRewardAmounts(draft, payableAmount),
35
- [draft, payableAmount]
36
- );
37
-
38
- const grouped = useMemo(() => {
39
- const map: Record<RewardCategory, RewardItem[]> = {
40
- special: [],
41
- general: []
42
- };
43
- rewards.forEach((reward) => {
44
- if (map[reward.type]) {
45
- map[reward.type].push(reward);
46
- }
47
- });
48
- return map;
49
- }, [rewards]);
50
-
51
- const toggleReward = (reward: RewardItem) => {
52
- setDraft((current) => {
53
- const exists = current.some((item) => isSameReward(item, reward));
54
- if (exists) {
55
- return current.filter((item) => !isSameReward(item, reward));
56
- }
57
- const withoutSameType = current.filter(
58
- (item) => item.type !== reward.type
59
- );
60
- return [...withoutSameType, reward];
61
- });
62
- };
63
-
64
- const isSelected = (reward: RewardItem) =>
65
- draft.some((item) => isSameReward(item, reward));
66
-
67
- const categoryLabel = (category: RewardCategory) =>
68
- category === 'special'
69
- ? texts.rewardCategorySpecialText
70
- : texts.rewardCategoryGeneralText;
71
-
72
- const handleConfirm = async () => {
73
- await onConfirm(draft);
74
- };
75
-
76
- const hasRewards = rewards.length > 0;
77
-
78
- return (
79
- <Modal
80
- portalId="masterpass-reward-modal"
81
- open={open}
82
- setOpen={onClose}
83
- title={texts.rewardModalTitle}
84
- className="w-full sm:w-[32rem] max-h-[90vh] overflow-y-auto"
85
- >
86
- <div className="px-6 pt-4 pb-6">
87
- {texts.rewardModalDescription && (
88
- <p className="text-sm text-gray-600 mb-4">
89
- {texts.rewardModalDescription}
90
- </p>
91
- )}
92
-
93
- {!hasRewards ? (
94
- <p className="text-center text-gray-500 py-8">
95
- {texts.rewardModalEmptyMessage}
96
- </p>
97
- ) : (
98
- <div className="space-y-5">
99
- {(Object.keys(grouped) as RewardCategory[])
100
- .filter((category) => grouped[category].length > 0)
101
- .map((category) => (
102
- <div key={category}>
103
- <h3 className="text-sm font-semibold text-gray-700 mb-2">
104
- {categoryLabel(category)}
105
- </h3>
106
- <ul className="space-y-2">
107
- {grouped[category].map((reward) => {
108
- const selected = isSelected(reward);
109
- const fullValue = parseRewardAmount(reward.amount);
110
- const capped = cappedAmounts[reward.type];
111
- const isCapped = selected && capped < fullValue;
112
- return (
113
- <li key={`${reward.type}-${reward.name ?? ''}`}>
114
- <label
115
- className={`flex items-center justify-between gap-3 px-3 py-3 border cursor-pointer transition-colors ${
116
- selected
117
- ? 'border-primary bg-primary/5'
118
- : 'border-gray-200 hover:bg-gray-50'
119
- }`}
120
- >
121
- <div className="flex items-center gap-3">
122
- <input
123
- type="checkbox"
124
- className="h-4 w-4"
125
- checked={selected}
126
- onChange={() => toggleReward(reward)}
127
- disabled={isLoading}
128
- />
129
- <div>
130
- <p className="font-medium">
131
- {reward.name || categoryLabel(reward.type)}
132
- </p>
133
- {reward.name && (
134
- <p className="text-xs text-gray-500">
135
- {categoryLabel(reward.type)}
136
- </p>
137
- )}
138
- </div>
139
- </div>
140
- <div className="text-right">
141
- <span
142
- className={`font-semibold ${
143
- isCapped
144
- ? 'text-gray-400 line-through text-sm'
145
- : ''
146
- }`}
147
- >
148
- {formatAmount(reward.amount, currency)}
149
- </span>
150
- {isCapped && (
151
- <p className="text-xs text-[#00a63d] font-medium mt-0.5">
152
- {(texts.rewardCappedNoticeText || '').replace(
153
- '{amount}',
154
- formatAmount(capped, currency)
155
- )}
156
- </p>
157
- )}
158
- </div>
159
- </label>
160
- </li>
161
- );
162
- })}
163
- </ul>
164
- </div>
165
- ))}
166
- </div>
167
- )}
168
-
169
- <div className="flex gap-3 justify-end mt-6">
170
- <Button
171
- appearance="outlined"
172
- className="px-5 py-3 h-auto"
173
- onClick={onClose}
174
- disabled={isLoading}
175
- >
176
- {texts.rewardModalCancelText}
177
- </Button>
178
- <Button
179
- appearance="filled"
180
- className="px-5 py-3 h-auto"
181
- onClick={handleConfirm}
182
- disabled={isLoading || !hasRewards}
183
- >
184
- {isLoading
185
- ? texts.rewardModalLoadingText
186
- : texts.rewardModalConfirmText}
187
- </Button>
188
- </div>
189
- </div>
190
- </Modal>
191
- );
192
- };
193
-
194
- export default RewardSelectionModal;
@@ -1,41 +0,0 @@
1
- import type { RewardItem, RewardCategory } from '../types/payment.types';
2
-
3
- export const REWARD_PRIORITY: RewardCategory[] = ['special', 'general'];
4
-
5
- export const parseRewardAmount = (amount: number | string): number => {
6
- const value = typeof amount === 'string' ? parseFloat(amount) : amount;
7
- return Number.isFinite(value) ? value : 0;
8
- };
9
-
10
- export const getCappedRewardAmounts = (
11
- selected: RewardItem[],
12
- payableAmount?: string | number | null
13
- ): Record<RewardCategory, number> => {
14
- const parsedPayable =
15
- payableAmount != null ? parseRewardAmount(payableAmount) : NaN;
16
- let remaining = Number.isFinite(parsedPayable) ? parsedPayable : Infinity;
17
-
18
- const result: Record<RewardCategory, number> = { special: 0, general: 0 };
19
-
20
- REWARD_PRIORITY.forEach((type) => {
21
- const picked = selected.find((item) => item.type === type);
22
- if (!picked) return;
23
-
24
- const value = parseRewardAmount(picked.amount);
25
- if (value <= 0) return;
26
-
27
- const used = Math.min(value, remaining);
28
- remaining = Math.max(remaining - used, 0);
29
- result[type] = used;
30
- });
31
-
32
- return result;
33
- };
34
-
35
- export const getCappedRewardTotal = (
36
- selected: RewardItem[],
37
- payableAmount?: string | number | null
38
- ): number => {
39
- const amounts = getCappedRewardAmounts(selected, payableAmount);
40
- return REWARD_PRIORITY.reduce((sum, type) => sum + amounts[type], 0);
41
- };