fix payments save method
This commit is contained in:
@@ -0,0 +1,257 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import BankCardInput from '@/app/ui/inputs/bank-card-inpit';
|
||||||
|
import { useActionState, useEffect, ChangeEvent, useState } from 'react';
|
||||||
|
import { addPayoutMethod, getPayoutMethods, PayoutMethods, PayoutMethodFormState } from '@/app/actions/referralsActions';
|
||||||
|
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
import valid from 'card-validator';
|
||||||
|
import { bankCardFormSchema } from '@/app/actions/definitions';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
|
||||||
|
interface cardInfoType {
|
||||||
|
cardType: string,
|
||||||
|
potentiallyValid: boolean,
|
||||||
|
cardValid?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BankCard() {
|
||||||
|
const [state, formAction, isPending] = useActionState(
|
||||||
|
addPayoutMethod,
|
||||||
|
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<PayoutMethodFormState | undefined>(undefined);
|
||||||
|
const [defaultCardNumber, setDefaultCardNumber] = useState<PayoutMethods | undefined>(undefined);
|
||||||
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
|
const [cardInfo, setCardInfo] = useState<null | cardInfoType>(null);
|
||||||
|
const [initialCardInfo, setInitialCardInfo] = useState<null | cardInfoType>(null);
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const t = useTranslations('Global');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setFormState(state);
|
||||||
|
if (state?.success) {
|
||||||
|
toast.success('success');
|
||||||
|
setIsEditing(false);
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['payoutMethods'] });
|
||||||
|
}
|
||||||
|
|
||||||
|
}, [state]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (payoutMethods) {
|
||||||
|
payoutMethods.forEach(e => {
|
||||||
|
if (e.default) {
|
||||||
|
const numberValidation = valid.number(e.cardNumber);
|
||||||
|
if (numberValidation.card) {
|
||||||
|
setCardInfo({
|
||||||
|
cardType: numberValidation.card.type,
|
||||||
|
potentiallyValid: numberValidation.isPotentiallyValid
|
||||||
|
});
|
||||||
|
setInitialCardInfo({
|
||||||
|
cardType: numberValidation.card.type,
|
||||||
|
potentiallyValid: numberValidation.isPotentiallyValid
|
||||||
|
})
|
||||||
|
}
|
||||||
|
setDefaultCardNumber(e);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [payoutMethods])
|
||||||
|
|
||||||
|
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);
|
||||||
|
const numberValidation = valid.number(e.target.value);
|
||||||
|
|
||||||
|
if (numberValidation.card) {
|
||||||
|
setCardInfo({
|
||||||
|
cardType: numberValidation.card.type,
|
||||||
|
potentiallyValid: numberValidation.isPotentiallyValid,
|
||||||
|
cardValid: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.target.value.length === 19 && !numberValidation.isValid) {
|
||||||
|
setCardInfo((prev) => {
|
||||||
|
if (prev) {
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
cardValid: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validatedField.success) {
|
||||||
|
setFormState(prevState => {
|
||||||
|
if (!prevState?.error || !prevState?.error[fieldName]) {
|
||||||
|
return {
|
||||||
|
previousState: prevState?.previousState,
|
||||||
|
error: {
|
||||||
|
server: null
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const { [fieldName]: _, ...newError } = prevState.error;
|
||||||
|
|
||||||
|
return {
|
||||||
|
previousState: prevState?.previousState,
|
||||||
|
error: {
|
||||||
|
...newError,
|
||||||
|
server: null
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form action={(e) => {
|
||||||
|
formAction(e);
|
||||||
|
}}>
|
||||||
|
<div>
|
||||||
|
<h4>
|
||||||
|
{t('bank-card-number')}
|
||||||
|
</h4>
|
||||||
|
<div
|
||||||
|
className="relative"
|
||||||
|
>
|
||||||
|
<BankCardInput
|
||||||
|
bankCardState={defaultCardNumber?.cardNumber}
|
||||||
|
validateHandler={onChangeHandler}
|
||||||
|
disabled={!isEditing}
|
||||||
|
isValid={cardInfo?.cardValid}
|
||||||
|
/>
|
||||||
|
{/* */}
|
||||||
|
{cardInfo && (
|
||||||
|
<div
|
||||||
|
className="card-type"
|
||||||
|
>
|
||||||
|
{cardInfo.cardType}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{cardInfo?.cardValid === false && (
|
||||||
|
<p className="text-sm text-red-500">
|
||||||
|
<span>
|
||||||
|
{t('bank-card-not-valid')}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{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>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{
|
||||||
|
isEditing ? (
|
||||||
|
<div
|
||||||
|
className='flex gap-4'
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn-primary"
|
||||||
|
disabled={isPending}
|
||||||
|
onClick={() => {
|
||||||
|
setIsEditing(false);
|
||||||
|
setCardInfo(initialCardInfo);
|
||||||
|
setFormState(prevState => {
|
||||||
|
if (!prevState?.error || !prevState?.error['bankCard']) {
|
||||||
|
return {
|
||||||
|
previousState: prevState?.previousState,
|
||||||
|
error: {
|
||||||
|
server: null
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('cancel')}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="btn-primary"
|
||||||
|
disabled={isPending || !cardInfo?.cardValid}
|
||||||
|
>
|
||||||
|
{t("save-changes")}
|
||||||
|
{isPending && (
|
||||||
|
<div className="loading-animation">
|
||||||
|
<div className="global-spinner"></div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn-primary"
|
||||||
|
disabled={isPending}
|
||||||
|
onClick={() => {
|
||||||
|
setIsEditing(true)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Изменить
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -2,150 +2,13 @@
|
|||||||
|
|
||||||
import DropDownList from '@/app/components/DropDownList';
|
import DropDownList from '@/app/components/DropDownList';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import { ChangeEvent, useState, useActionState, useEffect } from 'react';
|
import { useState } from 'react';
|
||||||
import BankCardInput from '@/app/ui/inputs/bank-card-inpit';
|
import BankCard from '@/app/ui/referral-page/payout-settings/bank-card';
|
||||||
import { addPayoutMethod, PayoutMethodFormState, getPayoutMethods, PayoutMethods } from '@/app/actions/referralsActions';
|
|
||||||
import { bankCardFormSchema } from '@/app/actions/definitions';
|
|
||||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
|
||||||
import { toast } from 'sonner';
|
|
||||||
import valid from 'card-validator';
|
|
||||||
|
|
||||||
interface cardInfoType {
|
|
||||||
cardType: string,
|
|
||||||
potentiallyValid: boolean,
|
|
||||||
cardValid?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function SettingReferralProgram() {
|
export default function SettingReferralProgram() {
|
||||||
const [state, formAction, isPending] = useActionState(
|
|
||||||
addPayoutMethod,
|
|
||||||
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<PayoutMethodFormState | undefined>(undefined);
|
|
||||||
const [paymentsType, setPaymentsType] = useState('bank-card');
|
const [paymentsType, setPaymentsType] = useState('bank-card');
|
||||||
const [defaultCardNumber, setDefaultCardNumber] = useState<PayoutMethods | undefined>(undefined);
|
|
||||||
const [isEditing, setIsEditing] = useState(false);
|
|
||||||
const [cardInfo, setCardInfo] = useState<null | cardInfoType>(null);
|
|
||||||
const [initialCardInfo, setInitialCardInfo] = useState<null | cardInfoType>(null);
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
const t = useTranslations('Global');
|
const t = useTranslations('Global');
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setFormState(state);
|
|
||||||
if (state?.success) {
|
|
||||||
toast.success('success');
|
|
||||||
setIsEditing(false);
|
|
||||||
queryClient.invalidateQueries({ queryKey: ['payoutMethods'] });
|
|
||||||
}
|
|
||||||
|
|
||||||
}, [state]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (payoutMethods) {
|
|
||||||
payoutMethods.forEach(e => {
|
|
||||||
if (e.default) {
|
|
||||||
const numberValidation = valid.number(e.cardNumber);
|
|
||||||
if (numberValidation.card) {
|
|
||||||
setCardInfo({
|
|
||||||
cardType: numberValidation.card.type,
|
|
||||||
potentiallyValid: numberValidation.isPotentiallyValid
|
|
||||||
});
|
|
||||||
setInitialCardInfo({
|
|
||||||
cardType: numberValidation.card.type,
|
|
||||||
potentiallyValid: numberValidation.isPotentiallyValid
|
|
||||||
})
|
|
||||||
}
|
|
||||||
setDefaultCardNumber(e);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, [payoutMethods])
|
|
||||||
|
|
||||||
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);
|
|
||||||
const numberValidation = valid.number(e.target.value);
|
|
||||||
|
|
||||||
if (numberValidation.card) {
|
|
||||||
setCardInfo({
|
|
||||||
cardType: numberValidation.card.type,
|
|
||||||
potentiallyValid: numberValidation.isPotentiallyValid,
|
|
||||||
cardValid: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.target.value.length === 19 && !numberValidation.isValid) {
|
|
||||||
setCardInfo((prev) => {
|
|
||||||
if (prev) {
|
|
||||||
return {
|
|
||||||
...prev,
|
|
||||||
cardValid: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (validatedField.success) {
|
|
||||||
setFormState(prevState => {
|
|
||||||
if (!prevState?.error || !prevState?.error[fieldName]) {
|
|
||||||
return {
|
|
||||||
previousState: prevState?.previousState,
|
|
||||||
error: {
|
|
||||||
server: null
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const { [fieldName]: _, ...newError } = prevState.error;
|
|
||||||
|
|
||||||
return {
|
|
||||||
previousState: prevState?.previousState,
|
|
||||||
error: {
|
|
||||||
...newError,
|
|
||||||
server: null
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="block-wrapper setting-referral-program">
|
<div className="block-wrapper setting-referral-program">
|
||||||
|
|
||||||
@@ -153,10 +16,7 @@ export default function SettingReferralProgram() {
|
|||||||
{t('referral-program-settings')}
|
{t('referral-program-settings')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form
|
<div
|
||||||
action={(e) => {
|
|
||||||
formAction(e);
|
|
||||||
}}
|
|
||||||
className="settings-form"
|
className="settings-form"
|
||||||
>
|
>
|
||||||
<div className="settings-form-wrapper">
|
<div className="settings-form-wrapper">
|
||||||
@@ -199,110 +59,8 @@ export default function SettingReferralProgram() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{paymentsType === 'bank-card' && (
|
{paymentsType === 'bank-card' && (
|
||||||
<div>
|
<BankCard />
|
||||||
<h4>
|
|
||||||
{t('bank-card-number')}
|
|
||||||
</h4>
|
|
||||||
<div
|
|
||||||
className="relative"
|
|
||||||
>
|
|
||||||
<BankCardInput
|
|
||||||
bankCardState={defaultCardNumber?.cardNumber}
|
|
||||||
validateHandler={onChangeHandler}
|
|
||||||
disabled={!isEditing}
|
|
||||||
isValid={cardInfo?.cardValid}
|
|
||||||
/>
|
|
||||||
{/* */}
|
|
||||||
{cardInfo && (
|
|
||||||
<div
|
|
||||||
className="card-type"
|
|
||||||
>
|
|
||||||
{cardInfo.cardType}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{cardInfo?.cardValid === false && (
|
|
||||||
<p className="text-sm text-red-500">
|
|
||||||
<span>
|
|
||||||
{t('bank-card-not-valid')}
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
{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>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{
|
|
||||||
isEditing ? (
|
|
||||||
<div
|
|
||||||
className='flex gap-4'
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="btn-primary"
|
|
||||||
disabled={isPending}
|
|
||||||
onClick={() => {
|
|
||||||
setIsEditing(false);
|
|
||||||
setCardInfo(initialCardInfo);
|
|
||||||
setFormState(prevState => {
|
|
||||||
if (!prevState?.error || !prevState?.error['bankCard']) {
|
|
||||||
return {
|
|
||||||
previousState: prevState?.previousState,
|
|
||||||
error: {
|
|
||||||
server: null
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t('cancel')}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
className="btn-primary"
|
|
||||||
disabled={isPending || !cardInfo?.cardValid}
|
|
||||||
>
|
|
||||||
{t("save-changes")}
|
|
||||||
{isPending && (
|
|
||||||
<div className="loading-animation">
|
|
||||||
<div className="global-spinner"></div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="btn-primary"
|
|
||||||
disabled={isPending}
|
|
||||||
onClick={() => {
|
|
||||||
setIsEditing(true)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Изменить
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -314,7 +72,7 @@ export default function SettingReferralProgram() {
|
|||||||
<label>{t('email-notifications-about-new-referrals')}</label>
|
<label>{t('email-notifications-about-new-referrals')}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user