From 454ec6f865a583d09f429456d2fdc424622748a9 Mon Sep 17 00:00:00 2001 From: smanylov Date: Tue, 24 Feb 2026 17:22:01 +0700 Subject: [PATCH] add PayoutMethod, getReferralPayments, add translates --- src/app/[locale]/pages/refferals/page.tsx | 1 - src/app/actions/definitions.ts | 12 +- src/app/actions/referralsActions.ts | 137 +++++++++++++- src/app/components/DropDownList.tsx | 1 + .../react-query/useGetReferralPayments.ts | 39 ++++ src/app/ui/forms/register-form.tsx | 2 +- src/app/ui/inputs/bank-card-inpit.tsx | 32 ++++ .../ui/referral-page/Income-and-payments.tsx | 38 +--- .../ui/referral-page/invitations-table.tsx | 2 +- .../setting-referral-program.tsx | 172 ++++++++++++++---- .../referral-page/table/payments-history.tsx | 52 ++++++ src/i18n/messages/en.json | 12 +- src/i18n/messages/ru.json | 12 +- 13 files changed, 429 insertions(+), 83 deletions(-) create mode 100644 src/app/hooks/react-query/useGetReferralPayments.ts create mode 100644 src/app/ui/inputs/bank-card-inpit.tsx create mode 100644 src/app/ui/referral-page/table/payments-history.tsx diff --git a/src/app/[locale]/pages/refferals/page.tsx b/src/app/[locale]/pages/refferals/page.tsx index 7cc1a5f..f3d1950 100644 --- a/src/app/[locale]/pages/refferals/page.tsx +++ b/src/app/[locale]/pages/refferals/page.tsx @@ -1,6 +1,5 @@ import { useTranslations } from 'next-intl'; import { Suspense } from 'react'; -import PageTitle from '@/app/ui/page-title'; import ReferralStats from '@/app/ui/referral-page/referral-stats'; import ReferralLinkSection from '@/app/ui/referral-page/referral-link-section'; import CommissionLevels from '@/app/ui/referral-page/commission-levels'; diff --git a/src/app/actions/definitions.ts b/src/app/actions/definitions.ts index fd774a0..37c0062 100644 --- a/src/app/actions/definitions.ts +++ b/src/app/actions/definitions.ts @@ -1,5 +1,5 @@ import * as z from 'zod' -import {createValidationSchema, createValidationSchemaForPassword} from '@/app/lib/validation-config-parser'; +import { createValidationSchema, createValidationSchemaForPassword } from '@/app/lib/validation-config-parser'; import validationConfig from '@/app/lib/validation-config.json'; export type FormState = @@ -93,4 +93,12 @@ export const testUserData = { fullName: 'test', email: 'test@mail.com', subscriptionType: 'base' -} \ No newline at end of file +} + +export const bankCardFormSchema = z + .object({ + bankCard: z + .string() + .min(19, { error: 'error-bank-card' }) + .trim() + }) \ No newline at end of file diff --git a/src/app/actions/referralsActions.ts b/src/app/actions/referralsActions.ts index 783f9d0..9484ca8 100644 --- a/src/app/actions/referralsActions.ts +++ b/src/app/actions/referralsActions.ts @@ -1,7 +1,8 @@ 'use server' import { getSessionData } from '@/app/actions/session'; -import { API_BASE_URL } from '@/app/actions/definitions'; +import { API_BASE_URL, bankCardFormSchema } from '@/app/actions/definitions'; +import { FormState } from '@/app/actions/auth'; export async function fetchReferralsLevels() { const token = await getSessionData('token'); @@ -109,13 +110,6 @@ export async function fetchReferralInvitees() { if (response.ok) { let parsed = await response.json(); - console.log({ - action: 'invitees', - token: token, - pageSize: 10, - pageNumber: 0 - }) - console.log(parsed); if (parsed.message_code === 0) { return parsed.message_body; @@ -178,4 +172,131 @@ export async function referralRefill() { error: error } } +} + +export async function getUserPayments() { + const token = await getSessionData('token'); + console.log('getUserPayments'); + + try { + const response = await fetch(`${API_BASE_URL}/api/payouts/requests`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'Authorization': `Bearer ${token}`, + } + }); + + if (response.ok) { + const parsed = await response.json(); + console.log(parsed); + + return parsed; + } else { + return null + } + } catch (error) { + return null + } +} + +export type PayoutMethodFormState = { + previousState: { + bankCard?: string | undefined; + paymentsType?: 'bank-card' | 'bank-transfer' | 'on-account-balance'; + } | undefined, + error: Record | null +}; + +export async function addPayoutMethod( + state: PayoutMethodFormState | undefined, + formData: FormData, +): Promise { + const token = await getSessionData('token'); + + const bankCard = formData.get('bankCard') as string || ''; + const paymentsType = formData.get('paymentsType') as 'bank-card' | 'bank-transfer' | 'on-account-balance'; + + const validatedFields = bankCardFormSchema.safeParse({ + bankCard + }); + + + if (!validatedFields.success) { + const errors: Record = {} + JSON.parse(validatedFields.error.message).forEach((obj: { + message: string, + path: string + }) => { + if (errors[obj.path[0]]) { + errors[obj.path[0]] = `${errors[obj.path[0]]} ${obj.message}`; + } else { + errors[obj.path[0]] = obj.message; + } + }); + + return { + previousState: { + bankCard + }, + error: errors + }; + } + + /* return { + previousState: { + bankCard, + paymentsType + }, + error: null + }; */ + + try { + const { bankCard } = validatedFields.data; + + const response = await fetch(`${API_BASE_URL}/api/payouts/payout-method/card`, { + method: 'POST', + body: JSON.stringify({ + "cardNumber": bankCard.replaceAll(' ', '') + }), + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'Authorization': `Bearer ${token}`, + } + }); + + console.log(response); + + if (response.ok) { + const parsed = await response.json(); + console.log(parsed); + + return { + previousState: { + bankCard, + paymentsType + }, + error: null + }; + } else { + throw 'error-save'; + } + } catch (error: unknown) { + const errors: Record = {}; + errors['server'] = 'error-save' + + if (error) { + const typedError = error as any; + } + + return { + previousState: { + bankCard, + paymentsType + }, + error: errors + }; + } } \ No newline at end of file diff --git a/src/app/components/DropDownList.tsx b/src/app/components/DropDownList.tsx index ffcdd19..becbf0b 100644 --- a/src/app/components/DropDownList.tsx +++ b/src/app/components/DropDownList.tsx @@ -48,6 +48,7 @@ export default function DropDownList({ children, value, callBack, translatedValu setIsOpen(!isOpen) }} className="dropdown-button" + type="button" > diff --git a/src/app/hooks/react-query/useGetReferralPayments.ts b/src/app/hooks/react-query/useGetReferralPayments.ts new file mode 100644 index 0000000..5895f44 --- /dev/null +++ b/src/app/hooks/react-query/useGetReferralPayments.ts @@ -0,0 +1,39 @@ +import { useQuery } from '@tanstack/react-query'; +import {getUserPayments} from '@/app/actions/referralsActions'; + +export interface PaymentsData { + amount: number, + cancellationReason: string | null, + createdAt: string, + id: number, + operationType: string, + paymentUuid: string, + status: string, + updatedAt: string, +} + + +export interface UserPayments { + payments: PaymentsData[], + error: string | null +} + +export const useGetReferralPayments = () => { + return useQuery({ + queryKey: ['referralPayments'], + queryFn: getUserPayments, + select: (data): UserPayments => { +/* console.log('referralPayments'); + console.log(data); */ + if (!data) { + return { + payments: [], + error: null + } + } + + return data; + }, + retry: false + }); +}; \ No newline at end of file diff --git a/src/app/ui/forms/register-form.tsx b/src/app/ui/forms/register-form.tsx index 5ba7026..ed28130 100644 --- a/src/app/ui/forms/register-form.tsx +++ b/src/app/ui/forms/register-form.tsx @@ -84,7 +84,7 @@ export default function RegisterForm() { } type SignupFormSchema = Awaited>; - const [signupFormSchema, setSignupFormSchema] = useState(null);; + const [signupFormSchema, setSignupFormSchema] = useState(null); useEffect(() => { let isMounted = true; diff --git a/src/app/ui/inputs/bank-card-inpit.tsx b/src/app/ui/inputs/bank-card-inpit.tsx new file mode 100644 index 0000000..d0333a8 --- /dev/null +++ b/src/app/ui/inputs/bank-card-inpit.tsx @@ -0,0 +1,32 @@ +'use client' + +import { useState, ChangeEvent, useEffect } from 'react'; +import { InputMask } from '@react-input/mask'; +import styles from '@/app/styles/module/login.module.scss'; + +interface BankCardProps { + bankCardState: string | undefined; + validateHandler: (e: ChangeEvent) => void; + clearCondition?: string | undefined | null +} + +export default function BankCardInput({ bankCardState, validateHandler }: BankCardProps) { + const [bankCard, setBankCard] = useState(bankCardState ? bankCardState : ''); + + return ( + { + setBankCard(e.target.value); + validateHandler(e); + }} + placeholder="1234 1234 1234 1234" + /> + ) +} \ No newline at end of file diff --git a/src/app/ui/referral-page/Income-and-payments.tsx b/src/app/ui/referral-page/Income-and-payments.tsx index c196ec9..0bde0dd 100644 --- a/src/app/ui/referral-page/Income-and-payments.tsx +++ b/src/app/ui/referral-page/Income-and-payments.tsx @@ -1,3 +1,5 @@ +import PaymentsHistory from '@/app/ui/referral-page/table/payments-history'; + export default function IncomeAndPayments() { return (
@@ -51,41 +53,7 @@ export default function IncomeAndPayments() {
- -

📊 История выплат

-
-
- Сумма -
-
- Способ -
-
- Статус -
-
- Дата запроса -
-
- Обработано -
- -
- 0 -
-
- 0 -
-
- 0 -
-
- 0 -
-
- 0 -
-
+ ) } \ No newline at end of file diff --git a/src/app/ui/referral-page/invitations-table.tsx b/src/app/ui/referral-page/invitations-table.tsx index 33f3bbe..a64cfb2 100644 --- a/src/app/ui/referral-page/invitations-table.tsx +++ b/src/app/ui/referral-page/invitations-table.tsx @@ -47,7 +47,7 @@ export default function InvitationsTable() { ]; return data.map((item: InviteType) => { - console.log(item); + return { status: item?.active ? 'active' : 'not-active', email: item?.email, diff --git a/src/app/ui/referral-page/setting-referral-program.tsx b/src/app/ui/referral-page/setting-referral-program.tsx index 38cbab1..b42a379 100644 --- a/src/app/ui/referral-page/setting-referral-program.tsx +++ b/src/app/ui/referral-page/setting-referral-program.tsx @@ -1,53 +1,159 @@ +'use client' + +import DropDownList from '@/app/components/DropDownList'; +import { useTranslations } from 'next-intl'; +import { ChangeEvent, useState, useActionState, useEffect } from 'react'; +import BankCardInput from '@/app/ui/inputs/bank-card-inpit'; +import { addPayoutMethod, PayoutMethodFormState } from '@/app/actions/referralsActions'; +import { bankCardFormSchema } from '@/app/actions/definitions'; + export default function SettingReferralProgram() { + const [state, formAction, isPending] = useActionState( + addPayoutMethod, + undefined, + ); + + const [formState, setFormState] = useState(undefined); + const [paymentsType, setPaymentsType] = useState('bank-card'); + const t = useTranslations('Global'); + + useEffect(() => { + setFormState(state); + }, [state]); + + async function validateField( + fieldName: string, + value: string, + ) { + switch (fieldName) { + case 'bankCard': + const schema = bankCardFormSchema?.shape[fieldName]; + if (!schema) { + return { success: true, data: value }; + } + + return schema.safeParse(value); + + default: + throw new Error(`Unknown field: ${fieldName}`); + } + } + + async function onChangeHandler(e: ChangeEvent): Promise { + const fieldName = e.target.name; + + const validatedField = await validateField(fieldName, e.target.value); + + if (validatedField.success) { + setFormState(prevState => { + if (!prevState?.error || !prevState?.error[fieldName]) { + return { + previousState: prevState?.previousState, + error: {} + }; + } + + const { [fieldName]: _, ...newError } = prevState.error; + + return { + previousState: prevState?.previousState, + error: newError + }; + }); + } + } + return (
- ⚙️ - Настройки реферальной программы + {t('referral-program-settings')}
-
+ { + formAction(e); + }} + className="settings-form" + >
-

💳 Настройки выплат

+

{t('payout-settings')}

- - -
- -
- - -
- -
- - + + { + switch (paymentsType) { + case 'on-account-balance': + return t('on-account-balance') + case 'bank-card': + return t('bank-card') + case 'bank-transfer': + return t('bank-transfer') + } + })() + } + callBack={setPaymentsType} + > +
  • + {t('on-account-balance')} +
  • +
  • + {t('bank-card')} +
  • +
  • + {t('bank-transfer')} +
  • +
    +
    + {paymentsType === 'bank-card' && ( +
    +

    + {t('bank-card-number')} +

    + + {formState?.error?.bankCard && ( +

    + { + formState?.error?.bankCard.split('&').map((e, index) => { + return ( + + {t(e)} +
    +
    + ) + }) + } +

    + )} +
    + )} + {formState?.error?.server && ( +

    + {t(formState?.error?.server)} +

    + )} +
    -

    🔔 Уведомления

    +

    {t('notifications')}

    - - + +
    - -
    - - -
    - -
    diff --git a/src/app/ui/referral-page/table/payments-history.tsx b/src/app/ui/referral-page/table/payments-history.tsx new file mode 100644 index 0000000..51a4a09 --- /dev/null +++ b/src/app/ui/referral-page/table/payments-history.tsx @@ -0,0 +1,52 @@ +'use client' + +import { useGetReferralPayments } from '@/app/hooks/react-query/useGetReferralPayments'; +import { useEffect } from 'react'; + +export default function PaymentsHistory() { + + const { data: paymentsData } = useGetReferralPayments(); + + useEffect(() => { + console.log(paymentsData); + }, [paymentsData]) + + return ( + <> +

    📊 История выплат

    +
    +
    + Сумма +
    +
    + Способ +
    +
    + Статус +
    +
    + Дата запроса +
    +
    + Обработано +
    + +
    + 0 +
    +
    + 0 +
    +
    + 0 +
    +
    + 0 +
    +
    + 0 +
    +
    + + ) +} \ No newline at end of file diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 2200da7..a8d5c82 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -262,7 +262,17 @@ "found-on-internet": "Found on the internet", "no-similar-global-title": "No similar images found on the internet", "no-similar-global-description": "This may mean your image is unique and not used on other websites.", - "global-error-title": "Global search error:" + "global-error-title": "Global search error:", + "on-account-balance": "On the account balance", + "bank-card": "Bank card", + "bank-transfer": "Bank transfer", + "preferred-payment-method": "Preferred payment method", + "referral-program-settings": "Referral program settings", + "payout-settings": "Payout settings", + "email-notifications-about-new-referrals": "Email notifications about new referrals", + "bank-card-number": "Bank card number", + "error-bank-card": "Enter your bank card number", + "error-save": "Error saving" }, "Login-register-form": { "and": "and", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index 90ded61..1f96cf2 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -262,7 +262,17 @@ "found-on-internet": "Найдено в интернете", "no-similar-global-title": "Похожие изображения в интернете не найдены", "no-similar-global-description": "Это может означать, что ваше изображение уникально и не используется на других сайтах.", - "global-error-title": "Ошибка глобального поиска:" + "global-error-title": "Ошибка глобального поиска:", + "on-account-balance": "На баланс аккаунта", + "bank-card": "Банковская карта", + "bank-transfer": "Банковский перевод", + "preferred-payment-method": "Предпочитаемый способ выплаты", + "referral-program-settings": "Настройки реферальной программы", + "payout-settings": "Настройки выплат", + "email-notifications-about-new-referrals": "Email уведомления о новых рефералах", + "bank-card-number": "Номер банковской карты", + "error-bank-card": "Введите номер банковской карты", + "error-save": "Ошибка при сохранении" }, "Login-register-form": { "and": "и",