continue: made a payments method list

This commit is contained in:
smanylov
2026-04-03 14:52:31 +07:00
parent e75fcd7bff
commit 6cf7bd766d
8 changed files with 376 additions and 31 deletions
+31 -2
View File
@@ -5,7 +5,6 @@ import { getSessionData } from '@/app/actions/session';
export async function getPaymentsMethods() {
const token = await getSessionData('token');
console.log('getPaymentsMethods');
try {
const response = await fetch(`${API_BASE_URL}/api/payments/methods`, {
@@ -19,7 +18,7 @@ export async function getPaymentsMethods() {
if (response.ok) {
const parsed = await response.json();
console.log(parsed);
return parsed;
} else {
return null
@@ -27,4 +26,34 @@ export async function getPaymentsMethods() {
} catch (error) {
return null
}
}
export async function deletePaymentsMethods(cardId: string): Promise<boolean | undefined> {
if (!cardId) {
return
}
const token = await getSessionData('token');
try {
const response = await fetch(`${API_BASE_URL}/api/payments/methods/${cardId}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': `Bearer ${token}`,
}
});
if (response.ok) {
const parsed = await response.json();
if (parsed.message === 'Payment method deleted') {
return true;
}
} else {
return false
}
} catch (error) {
return false
}
}
@@ -1,12 +1,13 @@
import { useQuery } from '@tanstack/react-query';
import { getPaymentsMethods } from '@/app/actions/paymentAction';
/* export interface PaymentsMethods {
total_violations: number,
new_violations: number,
in_progress_violations: number,
resolved_violations: number
} */
export interface PaymentsMethods {
cardType: string,
expiryMonth: string,
expiryYear: string,
lastFour: string,
paymentMethodId: string
}
export const usePaymentsMethods = () => {
return useQuery({
@@ -14,7 +15,7 @@ export const usePaymentsMethods = () => {
queryFn: () => {
return getPaymentsMethods()
},
select: (data: any | null) => {
select: (data: PaymentsMethods[] | null) => {
if (!data) {
return null
}
+12
View File
@@ -151,6 +151,10 @@
display: inline-flex;
align-items: center;
justify-content: center;
&:disabled {
opacity: 0.5;
}
}
.btn-primary {
@@ -190,6 +194,14 @@
}
}
.btn-warning {
background: v.$color-warning;
&:hover {
background: v.$color-warning-hover;
}
}
.divider {
text-align: center;
margin: 20px 0;
+121 -1
View File
@@ -5250,8 +5250,128 @@
}
.payment-user-cards {
.card-wrapper {
h3 {
color: #1e293b;
align-items: center;
gap: 8px;
margin-bottom: 16px;
font-size: 1.1rem;
display: flex;
}
.cards-wrapper {
background: #f8fafc;
border: 1.5px dashed #d1d5db;
border-radius: 14px;
padding: 20px;
text-align: center;
}
.card-item {
display: flex;
gap: 10px;
margin-bottom: 10px;
justify-content: space-between;
border-radius: 14px;
&-info {
display: flex;
width: 100%;
max-width: 400px;
color: v.$text-s;
align-items: center;
position: relative;
}
&-type {
font-size: 10px;
color: v.$text-s;
margin-left: 5px;
position: absolute;
right: 10px;
/* top: 3px; */
@media (max-width: 490px) {
font-size: 8px;
}
}
&-expired {
font-size: 10px;
color: v.$text-s;
margin-left: 5px;
position: absolute;
right: 10px;
bottom: 3px;
@media (max-width: 490px) {
font-size: 8px;
}
}
&-confirm-window {
h4 {
text-align: center;
margin-bottom: 15px;
}
}
&-confirm-action {
display: flex;
gap: 10px;
justify-content: center;
.btn {
max-width: 140px;
width: 100%;
position: relative;
}
}
&:last-child {
margin-bottom: 0;
}
&:hover {
background: #e2e4e6;
}
input {
background: v.$white;
padding: 5px 10px;
@media (max-width: 490px) {
font-size: 14px;
}
}
.btn {
padding: 5px 10px;
}
}
&-empty {
.empty-card-title {
font-size: 14px;
color: #64748b;
font-weight: 500;
}
.empty-card-text {
font-size: 12px;
color: #9ca3af;
margin-top: 4px;
}
}
&-notification {
margin-top: 14px;
padding: 12px 16px;
background: #fafafa;
border-radius: 10px;
font-size: 11px;
color: #9ca3af;
line-height: 1.6;
}
}
+2
View File
@@ -17,6 +17,8 @@ $p-color-disabled: #6365f18e;
$s-color-disabled: #8a5cf663;
$border-color-1: #e2e8f0;
$border-color-1-hover: #b1b7be;
$color-warning: #fab005;
$color-warning-hover: #fa9805;
$color-image: #f08c00;
$color-video: #2f9e44;
+180 -19
View File
@@ -2,36 +2,197 @@
import { useTranslations } from 'next-intl'
import BankCardInput from '@/app/ui/inputs/bank-card-inpit';
import { usePaymentsMethods } from '@/app/hooks/react-query/usePaymentsMethods';
import { useEffect } from 'react';
import { usePaymentsMethods, PaymentsMethods } from '@/app/hooks/react-query/usePaymentsMethods';
import { useEffect, useState } from 'react';
import { deletePaymentsMethods } from '@/app/actions/paymentAction';
import { useQueryClient } from '@tanstack/react-query';
import { toast } from 'sonner';
import ModalWindow from '@/app/components/ModalWindow';
export default function PaymentUserCards() {
const t = useTranslations('Global');
const { data: paymentsMethods } = usePaymentsMethods();
const queryClient = useQueryClient();
const [openWindow, setOpenWindow] = useState<boolean>(false);
const [selectedCard, setSelectedCard] = useState<PaymentsMethods | null>(null);
const [modalLoading, setModalLoading] = useState(false);
useEffect(() => {
console.log(paymentsMethods);
}, [paymentsMethods])
function openConfirmWindowHandler(item: PaymentsMethods) {
setOpenWindow(true);
setSelectedCard(item);
}
async function removeCardHandler(id: string) {
setModalLoading(true);
try {
const response = await deletePaymentsMethods(id);
if (response) {
await queryClient.invalidateQueries({ queryKey: ['paymentsMethods'] });
toast.success('bank-card-has-been-successfully-deleted');
} else {
throw 'error'
}
} catch (error) {
toast.warning(t('failed-to-delete-bank-card'))
} finally {
setModalLoading(false);
setOpenWindow(false);
setSelectedCard(null);
}
}
const ConfirmModalWindow = () => {
return (
<div
className="card-item-confirm-window"
>
<h4>
{t('delete-a-bank-card')}?
</h4>
<div
className="card-item-confirm-action"
>
<button
className="btn btn-secondary"
onClick={() => {
setSelectedCard(null);
setOpenWindow(false);
}}
disabled={modalLoading}
>
{t('no')}
{modalLoading && (
<div
className="loading-animation"
>
<span
className="global-spinner"
>
</span>
</div>
)}
</button>
<button
className="btn btn-secondary"
onClick={() => {
if (selectedCard?.paymentMethodId) {
removeCardHandler(selectedCard?.paymentMethodId)
}
}}
disabled={modalLoading}
>
{t('yes')}
{modalLoading && (
<div
className="loading-animation"
>
<span
className="global-spinner"
>
</span>
</div>
)}
</button>
</div>
</div >
)
}
return (
<div
className="block-wrapper payment-user-cards"
>
<div
className="card-wrapper"
>
<BankCardInput
bankCardState={'1111 1111 1111 1111'}
validateHandler={() => { }}
disabled={true}
isValid={undefined}
/>
<ModalWindow state={openWindow} callBack={setOpenWindow}>
<ConfirmModalWindow />
</ModalWindow>
<h3>
{t('linked-bank-cards')}
</h3>
<button
className="btn btn-primary"
>
{t('remove')}
</button>
<div
className="cards-wrapper"
>
{paymentsMethods?.length !== 0 ? (
paymentsMethods?.map((item: PaymentsMethods) => {
return (
<div
className="card-item"
key={item?.paymentMethodId}
>
<div
className="card-item-info"
>
<BankCardInput
bankCardState={item?.lastFour}
validateHandler={() => { }}
disabled={true}
isValid={undefined}
/>
<span
className="card-item-type"
>
{item?.cardType}
</span>
{/* <span
className="card-item-expired"
>
<span
className="ml-1"
>
expired at: &nbsp;
</span>
<span>
{item?.expiryMonth}/
</span>
<span>
{item?.expiryYear}
</span>
</span> */}
</div>
<button
className="btn btn-warning"
onClick={() => {
openConfirmWindowHandler(item)
}}
>
{t('remove')}
</button>
</div>
)
})
) : (
<div
className="payment-user-cards-empty"
>
<div>
<p
className="empty-card-title"
>
{t('the-card-is-not-linked')}
</p>
<p
className="empty-card-text"
>
{t('the-card-is-linked-automatically-the-next-time-you-pay-for-a-subscription')}
<br />
{t('after-this-the-subscription-will-renew-automatically')}
</p>
</div>
</div>
)}
</div>
<div
className="payment-user-cards-notification"
>
{t('when-you-pay-for-a-subscription-your-card-is-saved-for-automatic-renewal')}&nbsp;
{t('the-debit-occurs-1-day-before-the-end-of-the-subscription')}&nbsp;
{t('you-can-unlink-your-card-and-cancel-auto-renewal-at-any-time')}
</div>
</div>
)
+11 -1
View File
@@ -390,7 +390,17 @@
"select-all": "Select all",
"text-area-error-fill-complaint-text": "Fill out the complaint text",
"to-view-a-file-match-click-on-the-card-from-the-list": "To view a file match, click on the card from the list",
"insufficient-funds-to-receive-payment": "Insufficient funds to receive payment"
"insufficient-funds-to-receive-payment": "Insufficient funds to receive payment",
"failed-to-delete-bank-card": "Failed to delete bank card",
"bank-card-has-been-successfully-deleted": "The bank card has been successfully deleted",
"linked-bank-cards": "Linked bank cards",
"the-card-is-not-linked": "The card is not linked",
"the-card-is-linked-automatically-the-next-time-you-pay-for-a-subscription": "The card is linked automatically the next time you pay for a subscription.",
"after-this-the-subscription-will-renew-automatically": "After this, the subscription will renew automatically.",
"when-you-pay-for-a-subscription-your-card-is-saved-for-automatic-renewal": "When you pay for a subscription, your card is saved for automatic renewal.",
"the-debit-occurs-1-day-before-the-end-of-the-subscription": "The debit occurs 1 day before the end of the subscription.",
"you-can-unlink-your-card-and-cancel-auto-renewal-at-any-time": "You can unlink your card and cancel auto-renewal at any time.",
"delete-a-bank-card": "Delete a bank card"
},
"Login-register-form": {
"and": "and",
+11 -1
View File
@@ -390,7 +390,17 @@
"select-all": "Выбрать все",
"text-area-error-fill-complaint-text": "Заполните текст жалобы",
"to-view-a-file-match-click-on-the-card-from-the-list": "Для просмотра совпадения по файлу нажмите на карточку из списка",
"insufficient-funds-to-receive-payment": "Недостаточно средств для получения выплаты"
"insufficient-funds-to-receive-payment": "Недостаточно средств для получения выплаты",
"failed-to-delete-bank-card": "Не удалось удалить банковскую карту",
"bank-card-has-been-successfully-deleted": "Банковская карта успешно удалена",
"linked-bank-cards": "Привязанные банковские карты",
"the-card-is-not-linked": "Карта не привязана",
"the-card-is-linked-automatically-the-next-time-you-pay-for-a-subscription": "Карта привязывается автоматически при следующей оплате подписки.",
"after-this-the-subscription-will-renew-automatically": "После этого подписка будет продлеваться автоматически.",
"when-you-pay-for-a-subscription-your-card-is-saved-for-automatic-renewal": "При оплате подписки ваша карта сохраняется для автоматического продления.",
"the-debit-occurs-1-day-before-the-end-of-the-subscription": "Списание происходит за 1 день до окончания подписки.",
"you-can-unlink-your-card-and-cancel-auto-renewal-at-any-time": "Вы можете отвязать карту и отменить автопродление в любой момент.",
"delete-a-bank-card": "Удалить банковскую карту"
},
"Login-register-form": {
"and": "и",