add PayoutMethod, getReferralPayments,

add translates
This commit is contained in:
smanylov
2026-02-24 17:22:01 +07:00
parent 97b24cfadf
commit 454ec6f865
13 changed files with 429 additions and 83 deletions
@@ -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';
+10 -2
View File
@@ -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'
}
}
export const bankCardFormSchema = z
.object({
bankCard: z
.string()
.min(19, { error: 'error-bank-card' })
.trim()
})
+129 -8
View File
@@ -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<string, string> | null
};
export async function addPayoutMethod(
state: PayoutMethodFormState | undefined,
formData: FormData,
): Promise<PayoutMethodFormState> {
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<string, string> = {}
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<string, string> = {};
errors['server'] = 'error-save'
if (error) {
const typedError = error as any;
}
return {
previousState: {
bankCard,
paymentsType
},
error: errors
};
}
}
+1
View File
@@ -48,6 +48,7 @@ export default function DropDownList({ children, value, callBack, translatedValu
setIsOpen(!isOpen)
}}
className="dropdown-button"
type="button"
>
<span className="flex items-center">
<span className="mr-2">
@@ -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
});
};
+1 -1
View File
@@ -84,7 +84,7 @@ export default function RegisterForm() {
}
type SignupFormSchema = Awaited<ReturnType<typeof getSignupFormSchema>>;
const [signupFormSchema, setSignupFormSchema] = useState<SignupFormSchema | null>(null);;
const [signupFormSchema, setSignupFormSchema] = useState<SignupFormSchema | null>(null);
useEffect(() => {
let isMounted = true;
+32
View File
@@ -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<HTMLInputElement>) => void;
clearCondition?: string | undefined | null
}
export default function BankCardInput({ bankCardState, validateHandler }: BankCardProps) {
const [bankCard, setBankCard] = useState<string | undefined>(bankCardState ? bankCardState : '');
return (
<InputMask
type="phone"
id="bankCard"
name="bankCard"
className={`${styles['form-input']}`}
mask="____ ____ ____ ____"
replacement={{ _: /\d/ }}
value={bankCard}
onChange={(e) => {
setBankCard(e.target.value);
validateHandler(e);
}}
placeholder="1234 1234 1234 1234"
/>
)
}
@@ -1,3 +1,5 @@
import PaymentsHistory from '@/app/ui/referral-page/table/payments-history';
export default function IncomeAndPayments() {
return (
<div className="block-wrapper income-and-payments">
@@ -51,41 +53,7 @@ export default function IncomeAndPayments() {
<button className="btn-success">💸 Заказать выплату</button>
</div>
</div>
<h4 className="mt-[30px]">📊 История выплат</h4>
<div className="stats-grid">
<div className="stats-item left-top-corner stats-header">
Сумма
</div>
<div className="stats-item stats-header">
Способ
</div>
<div className="stats-item stats-header">
Статус
</div>
<div className="stats-item stats-header">
Дата запроса
</div>
<div className="stats-item last-column right-top-corner stats-header">
Обработано
</div>
<div className="stats-item first-column left-bottom-corner">
0
</div>
<div className="stats-item second-row">
0
</div>
<div className="stats-item second-row">
0
</div>
<div className="stats-item second-row">
0
</div>
<div className="stats-item last-column right-bottom-corner">
0
</div>
</div>
<PaymentsHistory />
</div>
)
}
@@ -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,
@@ -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<PayoutMethodFormState | undefined>(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<HTMLInputElement>): Promise<void> {
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 (
<div className="block-wrapper setting-referral-program">
<div className="section-title">
<span className="icon"></span>
Настройки реферальной программы
{t('referral-program-settings')}
</div>
<form className="settings-form">
<form
action={(e) => {
formAction(e);
}}
className="settings-form"
>
<div className="settings-form-wrapper">
<h4>💳 Настройки выплат</h4>
<h4>{t('payout-settings')}</h4>
<div className="form-group">
<label>Минимальная сумма для вывода (Б)</label>
<input type="number" name="min_withdraw_amount" defaultValue="100.00" min="50" step="10" />
</div>
<div className="form-group">
<label>Предпочитаемый способ выплаты</label>
<select name="preferred_payout_method">
<option defaultValue="BALANCE">💳 На баланс аккаунта</option>
<option defaultValue="BANK_TRANSFER">🏦 Банковский перевод</option>
<option defaultValue="PAYPAL">💰 PayPal</option>
<option defaultValue="CARD">💳 Банковская карта</option>
</select>
</div>
<div className="checkbox-group">
<input type="checkbox" name="auto_withdraw" defaultValue="1" />
<label>Автоматический вывод при достижении минимума</label>
<label>{t('preferred-payment-method')}</label>
<DropDownList
value={paymentsType}
translatedValue={
(() => {
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}
>
<li value="on-account-balance">
{t('on-account-balance')}
</li>
<li value="bank-card">
{t('bank-card')}
</li>
<li value="bank-transfer">
{t('bank-transfer')}
</li>
</DropDownList>
<input
type="hidden"
id="paymentsType"
name="paymentsType"
className={``}
value={paymentsType}
/>
</div>
{paymentsType === 'bank-card' && (
<div>
<h4>
{t('bank-card-number')}
</h4>
<BankCardInput bankCardState={formState?.previousState?.bankCard} validateHandler={onChangeHandler} />
{formState?.error?.bankCard && (
<p className="text-sm text-red-500">
{
formState?.error?.bankCard.split('&').map((e, index) => {
return (
<span key={index}>
{t(e)}
<br />
</span>
)
})
}
</p>
)}
</div>
)}
{formState?.error?.server && (
<p className="text-sm text-red-500 mt-4">
{t(formState?.error?.server)}
</p>
)}
<button type="submit" className="btn-primary">
{t('save-changes')}
</button>
</div>
<div className="settings-form-wrapper">
<h4>🔔 Уведомления</h4>
<h4>{t('notifications')}</h4>
<div className="checkbox-group">
<input type="checkbox" name="email_notifications" defaultValue="1"/>
<label>Email уведомления о новых рефералах</label>
<input type="checkbox" name="email_notifications" defaultValue="1" />
<label>{t('email-notifications-about-new-referrals')}</label>
</div>
<div className="checkbox-group">
<input type="checkbox" name="marketing_emails" defaultValue="1" />
<label>Маркетинговые рассылки о программе</label>
</div>
<button type="submit" className="btn-primary">
💾 Сохранить настройки
</button>
</div>
</form>
@@ -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 (
<>
<h4 className="mt-[30px]">📊 История выплат</h4>
<div className="stats-grid">
<div className="stats-item left-top-corner stats-header">
Сумма
</div>
<div className="stats-item stats-header">
Способ
</div>
<div className="stats-item stats-header">
Статус
</div>
<div className="stats-item stats-header">
Дата запроса
</div>
<div className="stats-item last-column right-top-corner stats-header">
Обработано
</div>
<div className="stats-item first-column left-bottom-corner">
0
</div>
<div className="stats-item second-row">
0
</div>
<div className="stats-item second-row">
0
</div>
<div className="stats-item second-row">
0
</div>
<div className="stats-item last-column right-bottom-corner">
0
</div>
</div>
</>
)
}
+11 -1
View File
@@ -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",
+11 -1
View File
@@ -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": "и",