Copy import {
CardSelectionSectionProps,
InstallmentSectionProps
} from '@akinon/pz-saved-card/src/types';
import React, { useEffect, useState } from 'react';
import { Image } from '@akinon/next/components';
import { cardImages } from '@akinon/pz-saved-card/src/views/saved-card-option';
import clsx from 'clsx';
import { twMerge } from 'tailwind-merge';
import { useSetSavedCardInstallmentOptionMutation } from '@akinon/pz-saved-card/src/redux/api';
import { getPosError } from '@akinon/next/utils';
import { useAppSelector } from '@akinon/next/redux/hooks';
import { RootState } from '@theme/redux/store';
import { useLocalization } from '@akinon/next/hooks';
import { SwiperPagination, SwiperReact, SwiperSlide } from '@theme/components';
import PluginModule, { Component } from '@akinon/next/components/plugin-module';
const getCreditCardType = (maskedCardNumber: string): string => {
const cardNumber = maskedCardNumber.replace(/\D/g, '');
if (/^4/.test(cardNumber)) {
return 'visa';
} else if (/^5[1-5]/.test(cardNumber) || /^2[2-7]/.test(cardNumber)) {
return 'mastercard';
} else if (/^3[47]/.test(cardNumber)) {
return 'amex';
} else if (/^9/.test(cardNumber)) {
return 'troy';
} else {
return 'other';
}
};
function maskCreditCard(cardNumber: string, value: string): string {
const cleanNumber = cardNumber.replace(/\D/g, '');
const lastFourDigits = cleanNumber.slice(-4);
let cardType = 'Unknown';
if (cleanNumber.startsWith('4')) {
cardType = 'Visa';
} else if (cleanNumber.startsWith('5') || cleanNumber.startsWith('2')) {
cardType = 'Mastercard';
} else if (cleanNumber.startsWith('3')) {
cardType = 'American Express';
} else if (cleanNumber.startsWith('6')) {
cardType = 'Discover';
}
if (value == 'lastFourDigits') return `* ${lastFourDigits}`;
if ((value = 'cardType')) return cardType;
}
const SavedCard = () => {
const [viewCounter, setViewCounter] = useState(3);
const [formError, setFormError] = useState(null);
const handleViewMore = () => {
viewCounter === 3 ? setViewCounter(99) : setViewCounter(3);
};
useEffect(() => {
const posErrors = getPosError();
if (posErrors) {
setFormError(posErrors);
}
}, []);
return (
<PluginModule
component={Component.SavedCard}
props={{
texts: {
title: 'Pay with Saved Card',
button: 'Pay Now'
},
formProps: {
id: 'paymentForm'
},
cardSelectionWrapperClassName: 'w-full',
installmentWrapperClassName: '',
customRender: {
cardSelectionSection: ({
cards,
onSelect,
selectedCard,
register,
errors
}: CardSelectionSectionProps) => {
if (cards?.length === 0) {
return (
<div className="flex items-center gap-2.5 px-[1.875rem] py-2">
<span
id="saved-card-error"
className="text-sm font-bold text-gray-620"
>
No Card Added yet.
</span>
</div>
);
}
return (
<div className="relative ms-[30px]">
<SwiperReact
modules={[SwiperPagination]}
pagination={{
el: '.swiper-pagination',
type: 'bullets',
clickable: true,
bulletClass: 'swiper-pagination-bullet bg-black',
bulletActiveClass:
'swiper-pagination-bullet-active !bg-white !w-[16px] md:!w-[70px] !rounded-[10px]'
}}
slidesPerView={'auto'}
spaceBetween={16}
>
{cards.slice(0, viewCounter).map((card) => (
<SwiperSlide key={card.token} className="!w-auto">
<li
className={clsx(
'relative flex h-[91px] min-w-[137px] cursor-pointer flex-col justify-between rounded-xl border border-gray-380 bg-white p-3.5',
{
'border-primary': selectedCard?.token === card.token
}
)}
onClick={() => onSelect(card)}
>
<div className="flex justify-between">
<input
name="card"
type="radio"
checked={selectedCard?.token === card.token}
value={card.token}
id={card.token}
onChange={() => {}}
{...register('card')}
className={twMerge(
'h-4 w-4 appearance-none rounded-full border border-primary ring-1 ring-transparent transition-all',
'checked:border-4 checked:border-primary-foreground checked:bg-primary checked:ring-primary'
)}
/>
<Image
width={35}
height={24}
src={
cardImages[
getCreditCardType(card.masked_card_number)
].src
}
alt={card.name}
className="object-contain"
/>
</div>
<label className="flex flex-col">
<p className="text-xs font-semibold text-gray-620">
{maskCreditCard(
card.masked_card_number,
'cardType'
)}
</p>
<p className="text-end text-base font-semibold">
{maskCreditCard(
card.masked_card_number,
'lastFourDigits'
)}
</p>
</label>
</li>
</SwiperSlide>
))}
</SwiperReact>
{cards && cards?.length > 3 && (
<p
className="mt-5 cursor-pointer text-base font-medium text-primary underline"
onClick={handleViewMore}
>
{viewCounter == 3 ? 'View All' : 'View Less'}
</p>
)}
{errors.card && (
<div className="mt-3 w-full px-1 text-start text-xs text-error-650">
{errors.card?.message}
</div>
)}
{formError?.non_field_errors && (
<div
className="mt-3 w-full px-1 text-start text-xs text-error"
data-testid="checkout-form-error"
>
{formError.non_field_errors}
</div>
)}
{formError?.status && (
<div
className="mt-3 w-full px-1 text-start text-xs text-error"
data-testid="checkout-form-error"
>
{formError.status}
</div>
)}
</div>
);
},
installmentSection: ({
selectedCard,
installmentOptions
}: InstallmentSectionProps) => {
const [selectedCardToken, setSelectedCardToken] = useState(null);
const [setInstallment] = useSetSavedCardInstallmentOptionMutation();
const { currency } = useLocalization();
const { basket } = useAppSelector(
(state: RootState) => state.checkout.preOrder
);
const sendBankaciRequest = async (saved_card) => {
const basket_id = basket?.pk;
const basket_modified_date = basket?.modified_date;
const bankaci_req = {
basket_id: basket_id,
basket_modified_date: basket_modified_date,
masked_card_number: saved_card.masked_card_number,
card_token: saved_card.token,
currency: currency
};
const bankaci_response = await fetch('/api/bankaci/saved_card', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(bankaci_req)
});
if (bankaci_response.status !== 200) {
return;
}
const bankaci_json = await bankaci_response.json();
return bankaci_json;
};
useEffect(() => {
if (
selectedCard &&
installmentOptions.length > 0 &&
selectedCardToken !== selectedCard?.token
) {
sendBankaciRequest(selectedCard).finally(() => {
const firstOptionPk = installmentOptions[0].pk;
setInstallment({
installment: firstOptionPk
});
setSelectedCardToken(selectedCard?.token);
});
}
}, [installmentOptions, setInstallment, selectedCard]);
return <></>;
},
agreementAndSubmit: () => (
<div className="mt-4 lg:hidden">
<button
className="hidden"
id="checkout-saved-card-place-order"
></button>
</div>
)
}
}}
/>
);
};
export default SavedCard;