diff --git a/package.json b/package.json index 6bd52cf..bc0821a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "no-copy-frontend", - "version": "0.44.0", + "version": "0.45.0", "private": true, "scripts": { "dev": "next dev -p 2999", diff --git a/src/app/actions/referralsActions.ts b/src/app/actions/referralsActions.ts index 1242e01..c1fa91c 100644 --- a/src/app/actions/referralsActions.ts +++ b/src/app/actions/referralsActions.ts @@ -2,7 +2,6 @@ import { getSessionData } from '@/app/actions/session'; import { API_BASE_URL, bankCardFormSchema } from '@/app/actions/definitions'; -import { FormState } from '@/app/actions/auth'; export async function fetchReferralsLevels() { const token = await getSessionData('token'); @@ -174,9 +173,52 @@ export async function referralRefill() { } } +export interface PayoutMethods { + cardNumber: string, + id: number, + type: string, + displayName: string, + maskedDetails: string, + default: boolean, + verified: boolean +} + +export async function getPayoutMethods(): Promise<{ + methods: PayoutMethods[], + error: string | null +}> { + const token = await getSessionData('token'); + + try { + const response = await fetch(`${API_BASE_URL}/api/payouts/user/payout-methods`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'Authorization': `Bearer ${token}`, + } + }); + + if (response.ok) { + const parsed = await response.json(); + + return { + methods: parsed, + error: null + }; + } else { + throw 'error'; + } + } catch (error) { + return { + methods: [], + 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`, { @@ -190,7 +232,6 @@ export async function getUserPayments() { if (response.ok) { const parsed = await response.json(); - console.log(parsed); return { payments: parsed, @@ -220,6 +261,7 @@ export async function addPayoutMethod( formData: FormData, ): Promise { const token = await getSessionData('token'); + console.log('addPayoutMethod'); const bankCard = formData.get('bankCard') as string || ''; const paymentsType = formData.get('paymentsType') as 'bank-card' | 'bank-transfer' | 'on-account-balance'; @@ -250,14 +292,6 @@ export async function addPayoutMethod( }; } - /* return { - previousState: { - bankCard, - paymentsType - }, - error: null - }; */ - try { const { bankCard } = validatedFields.data; @@ -305,4 +339,76 @@ export async function addPayoutMethod( error: errors }; } +} + +export type PayoutRequest = { + previousState: { + amount?: string | undefined; + } | undefined, + error: string | null +}; + +export async function createPayoutRequest( + state: PayoutRequest | undefined, + formData: FormData, +): Promise { + const token = await getSessionData('token'); + const amount = formData.get('amount') as string || ''; + const payoutMethodId = formData.get('payoutMethodId') as string || ''; + console.log('createPayoutRequest'); + console.log(token); + + try { + const response = await fetch(`${API_BASE_URL}/api/payouts/create-request`, { + method: 'POST', + body: JSON.stringify({ + "amount": amount, + "payoutMethodId": payoutMethodId + }), + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, + } + }); + + console.log(response); + + if (response.status === 400) { + const responseText = await response.text(); + throw JSON.parse(responseText)?.message; + } + + if (response.ok) { + const parsed = await response.json(); + console.log(parsed); + + return { + previousState: { + amount + }, + error: null + }; + } else { + throw 'error-request'; + } + } catch (error: unknown) { + + if (error) { + const typedError = error as string; + + return { + previousState: { + amount + }, + error: typedError + }; + } + + return { + previousState: { + amount + }, + error: 'error-request' + }; + } } \ No newline at end of file diff --git a/src/app/styles/pages-styles.scss b/src/app/styles/pages-styles.scss index eefb587..bdeaef7 100644 --- a/src/app/styles/pages-styles.scss +++ b/src/app/styles/pages-styles.scss @@ -1394,6 +1394,15 @@ transition: all 0.3s ease; white-space: nowrap; height: 48px; + + &:disabled { + background: linear-gradient(135deg, v.$p-color-disabled, v.$s-color-disabled); + cursor: default; + } + } + + .dropdown-button { + padding: 12px 16px; } } } @@ -3963,6 +3972,10 @@ .loading-animation { display: block; text-align: center; + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); .global-spinner { display: block; diff --git a/src/app/ui/forms/login-form.tsx b/src/app/ui/forms/login-form.tsx index 7c033b2..a93d601 100644 --- a/src/app/ui/forms/login-form.tsx +++ b/src/app/ui/forms/login-form.tsx @@ -106,12 +106,11 @@ export default function LoginForm() { className={`${styles['btn']}`} disabled={isPending} > - {isPending ? ( + {t("sign-in")} + {isPending && (
- ) : ( - t("sign-in") )} diff --git a/src/app/ui/forms/register-form.tsx b/src/app/ui/forms/register-form.tsx index ed28130..e2a6875 100644 --- a/src/app/ui/forms/register-form.tsx +++ b/src/app/ui/forms/register-form.tsx @@ -581,12 +581,11 @@ export default function RegisterForm() { className={`${styles['btn']}`} disabled={isPending} > - {isPending ? ( + {t("create-an-account")} + {isPending && (
- ) : ( - t("create-an-account") )} diff --git a/src/app/ui/inputs/bank-card-inpit.tsx b/src/app/ui/inputs/bank-card-inpit.tsx index d0333a8..dc90b5f 100644 --- a/src/app/ui/inputs/bank-card-inpit.tsx +++ b/src/app/ui/inputs/bank-card-inpit.tsx @@ -1,17 +1,36 @@ 'use client' import { useState, ChangeEvent, useEffect } from 'react'; -import { InputMask } from '@react-input/mask'; +import { InputMask, format } from '@react-input/mask'; import styles from '@/app/styles/module/login.module.scss'; +import { PayoutMethods } from '@/app/actions/referralsActions'; interface BankCardProps { bankCardState: string | undefined; validateHandler: (e: ChangeEvent) => void; - clearCondition?: string | undefined | null + clearCondition?: string | undefined | null; + disabled: boolean; } -export default function BankCardInput({ bankCardState, validateHandler }: BankCardProps) { - const [bankCard, setBankCard] = useState(bankCardState ? bankCardState : ''); +export default function BankCardInput({ bankCardState, validateHandler, disabled }: BankCardProps) { + const [bankCard, setBankCard] = useState(''); + + useEffect(() => { + if (bankCardState) { + if (disabled) { + setBankCard(format(bankCardState, { + mask: "**** **** **** ____", + replacement: { _: /\d/ } + })); + } else { + setBankCard(format(bankCardState, { + mask: "____ ____ ____ ____", + replacement: { _: /\d/ } + })); + } + + } + }, [bankCardState, disabled]); return ( { setBankCard(e.target.value); validateHandler(e); diff --git a/src/app/ui/referral-page/Income-and-payments.tsx b/src/app/ui/referral-page/Income-and-payments.tsx index 341840b..76c8c3b 100644 --- a/src/app/ui/referral-page/Income-and-payments.tsx +++ b/src/app/ui/referral-page/Income-and-payments.tsx @@ -3,6 +3,7 @@ import PaymentsHistory from '@/app/ui/referral-page/table/payments-history'; import { useQuery } from '@tanstack/react-query'; import { fetchReferralUserStats } from '@/app/actions/referralsActions'; +import RequestPayment from '@/app/ui/referral-page/request-payment'; export default function IncomeAndPayments() { const { @@ -55,25 +56,8 @@ export default function IncomeAndPayments() { -
-

💸 Запросить выплату

-
-
- - -
-
- - -
- -
-
+ + ) diff --git a/src/app/ui/referral-page/request-payment.tsx b/src/app/ui/referral-page/request-payment.tsx new file mode 100644 index 0000000..38d78a5 --- /dev/null +++ b/src/app/ui/referral-page/request-payment.tsx @@ -0,0 +1,155 @@ +'use client' + +import DropDownList from '@/app/components/DropDownList'; +import { useTranslations } from 'next-intl'; +import { useState, useRef, useEffect, useActionState } from 'react'; +import { useClickOutside } from '@/app/hooks/useClickOutside'; +import { getPayoutMethods, PayoutMethods, createPayoutRequest } from '@/app/actions/referralsActions'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { toast } from 'sonner'; + +export default function RequestPayment() { + const { + data: payoutMethods, + error + } = useQuery({ + queryKey: ['payoutMethods'], + queryFn: () => { + return getPayoutMethods(); + }, + select: (data) => { + if (data?.methods) { + return data?.methods + } else { + return [] + }; + } + }); + + const [state, formAction, isPending] = useActionState( + createPayoutRequest, + undefined, + ); + + useEffect(() => { + if (state && !state.error) { + toast.success('payment request success'); + queryClient.invalidateQueries({ queryKey: ['referralUserStats'] }); + queryClient.invalidateQueries({ queryKey: ['referralInvitees'] }); + } else if (state?.error) { + toast.error(state.error); + } + }, [state]); + + const queryClient = useQueryClient(); + const [paymentsType, setPaymentsType] = useState(null); + const [isOpen, setIsOpen] = useState(false); + const dropdownRef = useRef(null); + + useEffect(() => { + if (payoutMethods) { + setPaymentsType(payoutMethods[0]); + } + }, [payoutMethods]) + + useClickOutside(dropdownRef, + () => { + setIsOpen(false); + }, + isOpen + ); + const t = useTranslations('Global'); + + + return ( +
{ + formAction(e); + }} + > +

Запросить выплату

+
+
+ + + +
+
+ + +
+ + {isOpen && ( +
+
    + {payoutMethods?.map(method => { + return ( +
  • { + setPaymentsType(method) + setIsOpen(false); + }} + > + {method.displayName} +
  • + ) + })} +
+
+ )} +
+
+ +
+
+ ) +} \ No newline at end of file diff --git a/src/app/ui/referral-page/setting-referral-program.tsx b/src/app/ui/referral-page/setting-referral-program.tsx index 3e5433a..182d719 100644 --- a/src/app/ui/referral-page/setting-referral-program.tsx +++ b/src/app/ui/referral-page/setting-referral-program.tsx @@ -4,8 +4,9 @@ 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 { addPayoutMethod, PayoutMethodFormState, getPayoutMethods, PayoutMethods } from '@/app/actions/referralsActions'; import { bankCardFormSchema } from '@/app/actions/definitions'; +import { useQuery } from '@tanstack/react-query'; export default function SettingReferralProgram() { const [state, formAction, isPending] = useActionState( @@ -13,14 +14,43 @@ export default function SettingReferralProgram() { undefined, ); + const { + data: payoutMethods, + error + } = useQuery({ + queryKey: ['payoutMethods'], + queryFn: () => { + return getPayoutMethods(); + }, + select: (data) => { + if (data?.methods) { + return data?.methods + } else { + return [] + }; + } + }); + const [formState, setFormState] = useState(undefined); const [paymentsType, setPaymentsType] = useState('bank-card'); + const [defaultCardNumber, setDefaultCardNumber] = useState(undefined); + const [isEditing, setIsEditing] = useState(false); const t = useTranslations('Global'); useEffect(() => { setFormState(state); }, [state]); + useEffect(() => { + if (payoutMethods) { + payoutMethods.forEach(e => { + if (e.default) { + setDefaultCardNumber(e); + } + }) + } + }, [payoutMethods]) + async function validateField( fieldName: string, value: string, @@ -125,7 +155,11 @@ export default function SettingReferralProgram() {

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

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

{ @@ -147,9 +181,50 @@ export default function SettingReferralProgram() { {t(formState?.error?.server)}

)} - + + { + isEditing ? ( +
+ + + +
+ ) : ( + + ) + } +
diff --git a/src/app/ui/referral-page/table/invitations-table.tsx b/src/app/ui/referral-page/table/invitations-table.tsx index a64cfb2..a0c37ae 100644 --- a/src/app/ui/referral-page/table/invitations-table.tsx +++ b/src/app/ui/referral-page/table/invitations-table.tsx @@ -57,8 +57,6 @@ export default function InvitationsTable() { }, }); - const queryClient = useQueryClient(); - // Состояния const [sorting, setSorting] = useState([]); const [columnFilters, setColumnFilters] = useState([]);