From 6cf7bd766d0606eb9b676c5580051031d53fe94c Mon Sep 17 00:00:00 2001 From: smanylov Date: Fri, 3 Apr 2026 14:52:31 +0700 Subject: [PATCH] continue: made a payments method list --- src/app/actions/paymentAction.ts | 33 ++- .../hooks/react-query/usePaymentsMethods.ts | 15 +- src/app/styles/global-styles.scss | 12 ++ src/app/styles/pages-styles.scss | 122 ++++++++++- src/app/styles/variable.scss | 2 + src/app/ui/payment/payment-user-cards.tsx | 199 ++++++++++++++++-- src/i18n/messages/en.json | 12 +- src/i18n/messages/ru.json | 12 +- 8 files changed, 376 insertions(+), 31 deletions(-) diff --git a/src/app/actions/paymentAction.ts b/src/app/actions/paymentAction.ts index 28840ad..0f69c51 100644 --- a/src/app/actions/paymentAction.ts +++ b/src/app/actions/paymentAction.ts @@ -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 { + 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 + } } \ No newline at end of file diff --git a/src/app/hooks/react-query/usePaymentsMethods.ts b/src/app/hooks/react-query/usePaymentsMethods.ts index cfa5648..bc66e75 100644 --- a/src/app/hooks/react-query/usePaymentsMethods.ts +++ b/src/app/hooks/react-query/usePaymentsMethods.ts @@ -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 } diff --git a/src/app/styles/global-styles.scss b/src/app/styles/global-styles.scss index d994340..5e4c05e 100644 --- a/src/app/styles/global-styles.scss +++ b/src/app/styles/global-styles.scss @@ -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; diff --git a/src/app/styles/pages-styles.scss b/src/app/styles/pages-styles.scss index bd60b56..2711554 100644 --- a/src/app/styles/pages-styles.scss +++ b/src/app/styles/pages-styles.scss @@ -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; } } \ No newline at end of file diff --git a/src/app/styles/variable.scss b/src/app/styles/variable.scss index c4150ce..f6902bf 100644 --- a/src/app/styles/variable.scss +++ b/src/app/styles/variable.scss @@ -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; diff --git a/src/app/ui/payment/payment-user-cards.tsx b/src/app/ui/payment/payment-user-cards.tsx index 7b8897b..396607b 100644 --- a/src/app/ui/payment/payment-user-cards.tsx +++ b/src/app/ui/payment/payment-user-cards.tsx @@ -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(false); + const [selectedCard, setSelectedCard] = useState(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 ( +
+

+ {t('delete-a-bank-card')}? +

+
+ + +
+
+ ) + } return (
-
- { }} - disabled={true} - isValid={undefined} - /> + + + +

+ {t('linked-bank-cards')} +

- +
+ {paymentsMethods?.length !== 0 ? ( + paymentsMethods?.map((item: PaymentsMethods) => { + return ( +
+
+ { }} + disabled={true} + isValid={undefined} + /> + + + {item?.cardType} + + +{/* + + expired at:   + + + {item?.expiryMonth}/ + + + {item?.expiryYear} + + */} +
+ + +
+ ) + }) + ) : ( +
+
+

+ {t('the-card-is-not-linked')} +

+

+ {t('the-card-is-linked-automatically-the-next-time-you-pay-for-a-subscription')} +
+ {t('after-this-the-subscription-will-renew-automatically')} +

+
+
+ )} +
+ +
+ {t('when-you-pay-for-a-subscription-your-card-is-saved-for-automatic-renewal')}  + {t('the-debit-occurs-1-day-before-the-end-of-the-subscription')}  + {t('you-can-unlink-your-card-and-cancel-auto-renewal-at-any-time')}
) diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 029332f..09cd9bd 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -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", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index dba01d7..3152274 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -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": "и",