@akinon/pz-saved-card 1.62.0-rc.24 → 1.63.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,113 +0,0 @@
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;
@@ -1,15 +0,0 @@
1
- export const getCreditCardType = (maskedCardNumber: string): string => {
2
- const cardNumber = maskedCardNumber.replace(/\D/g, '');
3
-
4
- if (/^4/.test(cardNumber)) {
5
- return 'visa';
6
- } else if (/^5[1-5]/.test(cardNumber)) {
7
- return 'mastercard';
8
- } else if (/^3[47]/.test(cardNumber)) {
9
- return 'amex';
10
- } else if (/^9/.test(cardNumber)) {
11
- return 'troy';
12
- } else {
13
- return 'other';
14
- }
15
- };
@@ -1,232 +0,0 @@
1
- 'use client';
2
-
3
- import * as yup from 'yup';
4
- import { yupResolver } from '@hookform/resolvers/yup';
5
- import { useForm } from 'react-hook-form';
6
- import React, { ReactElement, useEffect, useMemo } from 'react';
7
- import { useAppDispatch, useAppSelector } from '@akinon/next/redux/hooks';
8
- import {
9
- useCompleteSavedCardMutation,
10
- useGetSavedCardsQuery,
11
- useSetSavedCardMutation
12
- } from '../redux/api';
13
-
14
- import amex from '../../assets/img/amex.jpg';
15
- import mastercard from '../../assets/img/mastercard.png';
16
- import other from '../../assets/img/other.png';
17
- import troy from '../../assets/img/troy.png';
18
- import visa from '../../assets/img/visa.png';
19
- import { setCards } from '../redux/reducer';
20
- import { DeleteConfirmationModal } from '../components/delete-confirmation-modal';
21
- import {
22
- AgreementAndSubmitProps,
23
- CardSelectionSectionProps,
24
- ErrorTexts,
25
- InstallmentSectionProps,
26
- SavedCardOptionTexts
27
- } from '../types';
28
- import { CardSelectionSection } from '../components/card-selection-section';
29
- import { InstallmentSection } from '../components/installment-section';
30
- import { AgreementAndSubmit } from '../components/agreement-and-submit';
31
-
32
- export const cardImages = { amex, mastercard, troy, visa, other };
33
-
34
- type SavedCardOptionProps = {
35
- texts?: SavedCardOptionTexts;
36
- agreementCheckbox?: ReactElement;
37
- customRender?: {
38
- cardSelectionSection?: (props: CardSelectionSectionProps) => ReactElement;
39
- installmentSection?: (props: InstallmentSectionProps) => ReactElement;
40
- agreementAndSubmit?: (props: AgreementAndSubmitProps) => ReactElement;
41
- };
42
- formWrapperClassName?: string;
43
- cardSelectionWrapperClassName?: string;
44
- installmentWrapperClassName?: string;
45
- formProps?: React.FormHTMLAttributes<HTMLFormElement>;
46
- cardSelectionWrapperProps?: React.HTMLAttributes<HTMLDivElement>;
47
- installmentWrapperProps?: React.HTMLAttributes<HTMLDivElement>;
48
- };
49
-
50
- const defaultTranslations: SavedCardOptionTexts = {
51
- title: 'Pay with Saved Card',
52
- button: 'Continue with selected card',
53
- installment: {
54
- title: 'Installment Options'
55
- },
56
- errors: {
57
- required: 'This field is required'
58
- }
59
- };
60
-
61
- const mergeTranslations = (
62
- customTranslations: SavedCardOptionTexts,
63
- defaultTranslations: SavedCardOptionTexts
64
- ) => {
65
- return {
66
- ...defaultTranslations,
67
- ...customTranslations,
68
- installment: {
69
- ...defaultTranslations.installment,
70
- ...customTranslations.installment
71
- },
72
- errors: {
73
- ...defaultTranslations.errors,
74
- ...customTranslations.errors
75
- },
76
- deletePopup: {
77
- ...defaultTranslations.deletePopup,
78
- ...customTranslations.deletePopup
79
- }
80
- };
81
- };
82
-
83
- const createFormSchema = (errors: ErrorTexts) =>
84
- yup.object().shape({
85
- card: yup
86
- .string()
87
- .required(errors?.required ?? defaultTranslations.errors.required)
88
- .typeError(errors?.required ?? defaultTranslations.errors.required),
89
- agreement: yup
90
- .boolean()
91
- .oneOf([true], errors?.required ?? defaultTranslations.errors.required)
92
- });
93
-
94
- const SavedCardOption = ({
95
- texts = defaultTranslations,
96
- agreementCheckbox,
97
- customRender,
98
- formWrapperClassName = 'flex flex-wrap w-full',
99
- cardSelectionWrapperClassName = 'w-full flex flex-col xl:w-6/10',
100
- installmentWrapperClassName = 'w-full xl:w-4/10 xl:border-l xl:border-t-0',
101
- formProps = {},
102
- cardSelectionWrapperProps = {},
103
- installmentWrapperProps = {}
104
- }: SavedCardOptionProps) => {
105
- const mergedTexts = useMemo(
106
- () => mergeTranslations(texts, defaultTranslations),
107
- [texts]
108
- );
109
- const dispatch = useAppDispatch();
110
- const { data: savedCards } = useGetSavedCardsQuery();
111
- const [completeSavedCard] = useCompleteSavedCardMutation();
112
- const [setSavedCard] = useSetSavedCardMutation();
113
- const installmentOptions = useAppSelector(
114
- (state) => state.checkout.installmentOptions
115
- );
116
- const cards = useAppSelector((state) => state.savedCard.cards);
117
-
118
- const {
119
- register,
120
- handleSubmit,
121
- control,
122
- formState: { errors },
123
- setValue,
124
- watch
125
- } = useForm({
126
- resolver: yupResolver(createFormSchema(mergedTexts.errors))
127
- });
128
-
129
- const selectedCardToken = watch('card');
130
- const selectedCard = useMemo(
131
- () => cards?.find((card) => card.token === selectedCardToken),
132
- [cards, selectedCardToken]
133
- );
134
-
135
- const handleCardSelection = async (card) => {
136
- await setSavedCard({ card }).unwrap();
137
- setValue('card', card.token, { shouldValidate: true });
138
- };
139
-
140
- const onSubmit = async () => {
141
- try {
142
- await completeSavedCard({ agreement: true });
143
- } catch (error) {
144
- console.error('Error completing saved card:', error);
145
- }
146
- };
147
-
148
- useEffect(() => {
149
- if (savedCards?.results && !cards?.length) {
150
- dispatch(setCards(savedCards.results));
151
- }
152
- }, [savedCards, cards, dispatch]);
153
-
154
- return (
155
- <>
156
- <form
157
- className={formWrapperClassName}
158
- onSubmit={handleSubmit(onSubmit)}
159
- {...formProps}
160
- >
161
- <div
162
- className={cardSelectionWrapperClassName}
163
- {...cardSelectionWrapperProps}
164
- >
165
- {customRender?.cardSelectionSection ? (
166
- customRender.cardSelectionSection({
167
- title: mergedTexts.title,
168
- cards,
169
- selectedCard,
170
- onSelect: handleCardSelection,
171
- register,
172
- errors,
173
- dispatch
174
- })
175
- ) : (
176
- <CardSelectionSection
177
- title={mergedTexts.title}
178
- cards={cards}
179
- selectedCard={selectedCard}
180
- onSelect={handleCardSelection}
181
- register={register}
182
- errors={errors}
183
- dispatch={dispatch}
184
- />
185
- )}
186
- </div>
187
-
188
- <div
189
- className={installmentWrapperClassName}
190
- {...installmentWrapperProps}
191
- >
192
- {customRender?.installmentSection ? (
193
- customRender.installmentSection({
194
- title: mergedTexts.installment?.title,
195
- selectedCard,
196
- installmentOptions,
197
- translations: mergedTexts.installment,
198
- errors: errors.installment
199
- })
200
- ) : (
201
- <InstallmentSection
202
- title={mergedTexts.installment?.title}
203
- selectedCard={selectedCard}
204
- installmentOptions={installmentOptions}
205
- translations={mergedTexts.installment}
206
- errors={errors.installment}
207
- />
208
- )}
209
-
210
- {customRender?.agreementAndSubmit ? (
211
- customRender.agreementAndSubmit({
212
- agreementCheckbox,
213
- control,
214
- errors,
215
- buttonText: mergedTexts.button
216
- })
217
- ) : (
218
- <AgreementAndSubmit
219
- agreementCheckbox={agreementCheckbox}
220
- control={control}
221
- errors={errors}
222
- buttonText={mergedTexts.button}
223
- />
224
- )}
225
- </div>
226
- </form>
227
- <DeleteConfirmationModal translations={mergedTexts.deletePopup} />
228
- </>
229
- );
230
- };
231
-
232
- export default SavedCardOption;