Commit 3fd08b1b by Arjun Jhukal

Merge branch 'dev'

parents 56221569 76a219fb
......@@ -10,6 +10,7 @@ function LayoutContent({ children }: { children: React.ReactNode }) {
const searchParams = useSearchParams();
const visitorId = searchParams.get("visitor_id");
useEffect(() => {
if (visitorId) {
localStorage.setItem("visitor_id", visitorId);
......
......@@ -7,13 +7,11 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const frontendUrl = process.env.NEXT_PUBLIC_FRONTEND_URL!;
const apiUrl = process.env.NEXT_PUBLIC_BASE_URL!;
// ✅ Fetch Menus
const menuRes = await fetch(`${apiUrl}/api/general/menus`, {
next: { revalidate: 48600 },
});
const menuData = await menuRes.json();
// ✅ Fetch Games
const gameRes = await getAllGames();
const gameData = gameRes?.data?.data || [];
......@@ -21,17 +19,12 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
{
url: frontendUrl,
priority: 1
// lastModified: new Date(),
// changeFrequency: "monthly",
},
];
// ✅ Append /general/[slug]
if (menuData?.data?.length) {
const menuUrls: MetadataRoute.Sitemap = menuData.data.map((menu: any) => ({
url: `${frontendUrl}/general/${menu.slug}`,
// lastModified: new Date(),
// changeFrequency: "weekly",
priority: 0.9
}));
......@@ -42,11 +35,10 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
if (gameData.length) {
const gameUrls: MetadataRoute.Sitemap = gameData.map((game: any) => ({
url: `${frontendUrl}/exclusive-games/${game.id}`,
// lastModified: new Date(),
// changeFrequency: "weekly",
priority: 0.9
}));
urls.push(...gameUrls);
}
......
// "use client";
// import GlassWrapper from '@/components/molecules/GlassWrapper';
// import { useAppDispatch } from '@/hooks/hook';
// import BitCoinIcon from '@/icons/BitCoinIcon';
// import GoldCoinIcon from '@/icons/GoldCoinIcon';
// import { useDepositMutation } from '@/services/transaction';
// import { showToast, ToastVariant } from '@/slice/toastSlice';
// import { Box, Button } from '@mui/material';
// import { TickCircle } from '@wandersonalwes/iconsax-react';
// import Image from 'next/image';
// import { useRouter } from 'next/navigation';
// import React from 'react';
// type PaymentModeProps = "crypto" | "idem"
// export default function CheckoutPage({ amount, slug, bonus }: {
// amount: number;
// slug: string;
// bonus: number
// }) {
// const dispatch = useAppDispatch();
// const router = useRouter();
// const [getPaymentLink, { isLoading: gettingLink }] = useDepositMutation();
// const [currentPaymentMode, setCurrentPaymentMode] = React.useState("crypto");
// // console.log(pathname)
// return (
// <section className="checkout__root">
// <div className="grid grid-cols-12 gap-4 lg:gap-10 xl:gap-12">
// <div className="col-span-12 lg:col-span-4 ">
// <Box className="coin__card" sx={{
// borderRadius: "16px",
// padding: "16px",
// border: '1px solid #B801C0',
// background: 'linear-gradient(0deg, rgba(0, 0, 0, 0.20) 0%, rgba(0, 0, 0, 0.20) 100%), rgba(255, 255, 255, 0.10)',
// boxShadow: '0 0 24px 0 rgba(234, 3, 91, 0.20)'
// }}>
// <div className="title">
// <h2 className='text-[28px]'>${amount}</h2>
// </div>
// <div className="footer mt-10">
// <div className="coin-info flex justify-between items-center py-3 px-4"
// style={{
// borderRadius: "16px",
// background: "linear-gradient(0deg, rgba(234, 47, 231, 0.10) 0%, rgba(234, 47, 231, 0.10) 100%), rgba(255, 255, 255, 0.10)"
// }}
// >
// <div className="coin flex items-center gap-1">
// <GoldCoinIcon />
// <span className='text-[12px]'>Gold Coins</span>
// </div>
// <p>
// <strong className='text-[16px] block'>{amount ? amount * 100 : ""}</strong>
// </p>
// </div>
// <div className="coin-info flex justify-between items-center py-3 px-4 mt-1"
// style={{
// borderRadius: "16px",
// background: "linear-gradient(0deg, rgba(234, 47, 231, 0.10) 0%, rgba(234, 47, 231, 0.10) 100%), rgba(255, 255, 255, 0.10)"
// }}
// >
// <div className="coin flex items-center gap-1">
// <GoldCoinIcon />
// <span className='text-[12px]'>Free Sweeps Coins</span>
// </div>
// <p>
// <strong className='text-[16px] block'>{bonus}</strong>
// </p>
// </div>
// </div>
// </Box>
// </div>
// <div className="col-span-12 lg:col-span-8">
// <Box>
// <h1 className='mb-2 text-[24px] lg:text-[32px]'>Payment Method</h1>
// <p className='text-[11px] lg:text-[13px]'>To start playing and cashing out your winnings, you’ll need a crypto wallet to purchase E-Credits and receive payouts. Don't worry—it’s quick and easy!</p>
// <h2 className='text-[20px] lg:text-[24px] mt-8 mb-4'>Select payment method</h2>
// <div className="grid sm:grid-cols-2 mb-8 gap-6">
// <div className="col-span-1">
// <GlassWrapper>
// <div className="py-5 px-4 flex justify-between items-center cursor-pointer" onClick={() => setCurrentPaymentMode("crypto")} >
// <span className="text-[14px] flex items-center justify-start gap-2"><BitCoinIcon />Crypto Currency</span>
// {currentPaymentMode === "crypto" ? <TickCircle /> : ""}
// </div>
// </GlassWrapper>
// </div>
// {/* <div className="col-span-1">
// <GlassWrapper>
// <div className="py-5 px-4 flex justify-between items-center cursor-pointer" onClick={() => setCurrentPaymentMode("idem")}>
// <span className="text-[14px] flex items-center justify-start gap-2"><BitcoinRefresh />IDEM</span>
// {currentPaymentMode === "idem" ? <TickCircle /> : ""}
// </div>
// </GlassWrapper>
// </div> */}
// </div>
// <Button type='submit' variant='contained' color='primary' className='!mt-3' onClick={async () => {
// try {
// if (currentPaymentMode === "crypto") {
// const response = await getPaymentLink({
// id: slug,
// amount,
// type: currentPaymentMode as PaymentModeProps
// }).unwrap();
// router.replace(response?.data?.payment_url)
// }
// else if (currentPaymentMode === "idem") {
// const response = await getPaymentLink({
// id: slug,
// amount,
// type: currentPaymentMode as PaymentModeProps
// }).unwrap();
// const merchant_id = response?.data?.merchant_id;
// const currency = response?.data?.currency;
// const order_ref = response?.data?.payment_id;
// const form = document.createElement("form");
// form.method = "POST";
// form.action = response?.data?.payment_url;
// const fields = {
// merchant_id,
// amount,
// currency,
// order_ref,
// completed_url: `${process.env.NEXT_PUBLIC_FRONTEND_URL}/buy-coins/${slug}/success`
// };
// Object.entries(fields).forEach(([key, value]) => {
// const input = document.createElement("input");
// input.type = "hidden";
// input.name = key;
// input.value = value as string;
// form.appendChild(input);
// });
// document.body.appendChild(form);
// form.submit();
// }
// else {
// dispatch(
// showToast({
// message: "Please select prefered mode of payment.",
// variant: ToastVariant.INFO
// })
// )
// }
// }
// catch (e: any) {
// dispatch(
// showToast({
// message: e?.data?.message || "Something went wrong",
// variant: ToastVariant.ERROR
// })
// )
// }
// }}>{gettingLink ? "Proceeding to Payment..." : "Proceed to Payment"}</Button>
// <p className="text-[11px] leading-[120%] mt-8 mb-2 text-center">Powered By</p>
// <div className="flex justify-center items-center gap-4">
// <Image src="/assets/images/payment-01.png" alt='' width={78} height={24} />
// <Image src="/assets/images/payment-02.png" alt='' width={78} height={24} />
// <Image src="/assets/images/payment-03.png" alt='' width={78} height={24} />
// </div>
// </Box>
// </div>
// </div>
// </section>
// )
// }
"use client";
import GlassWrapper from '@/components/molecules/GlassWrapper';
......@@ -23,7 +201,6 @@ export default function CheckoutPage({ amount, slug, bonus }: {
const [getPaymentLink, { isLoading: gettingLink }] = useDepositMutation();
const [currentPaymentMode, setCurrentPaymentMode] = React.useState("crypto");
// console.log(pathname)
return (
<section className="checkout__root">
<div className="grid grid-cols-12 gap-4 lg:gap-10 xl:gap-12">
......@@ -79,6 +256,7 @@ export default function CheckoutPage({ amount, slug, bonus }: {
<p className='text-[11px] lg:text-[13px]'>To start playing and cashing out your winnings, you’ll need a crypto wallet to purchase E-Credits and receive payouts. Don't worry—it’s quick and easy!</p>
<h2 className='text-[20px] lg:text-[24px] mt-8 mb-4'>Select payment method</h2>
<div className="grid sm:grid-cols-2 mb-8 gap-6">
<div className="col-span-1">
<GlassWrapper>
......
"use client";
import GlassWrapper from "@/components/molecules/GlassWrapper";
import BitCoinIcon from "@/icons/BitCoinIcon";
import { Box, Button, InputLabel, Modal, OutlinedInput } from "@mui/material";
import { useAppDispatch } from "@/hooks/hook";
import { useGetMassPayPaymentFieldsMutation, useGetMassPayPaymentMethodsQuery } from "@/services/transaction";
import { showToast, ToastVariant } from "@/slice/toastSlice";
import { MasspayPaymentFields } from "@/types/transaction";
import { Box, Button, Modal } from "@mui/material";
import { BitcoinRefresh, SecuritySafe, TickCircle } from "@wandersonalwes/iconsax-react";
import { FormikProps } from "formik";
import Image from "next/image";
import React from "react";
import Link from "next/link";
import { useState } from "react";
import { WithdrawlFormValues } from ".";
import { RenderFields } from "./renderFields";
const ShimmerCard = () => (
<div className="col-span-1">
<GlassWrapper>
<div className="py-5 px-4 flex justify-between items-center animate-pulse">
<div className="flex items-center gap-2 flex-1">
<div className="w-5 h-5 bg-white/20 rounded-full"></div>
<div className="h-4 bg-white/20 rounded w-24"></div>
</div>
<div className="w-5 h-5 bg-white/20 rounded-full"></div>
</div>
</GlassWrapper>
</div>
);
export default function WithdrawlModal({
open,
handleClose,
formik,
wallet
isLoading
}: {
open: boolean;
handleClose: () => void;
formik: FormikProps<WithdrawlFormValues>;
wallet: string;
isLoading: boolean
}) {
const [isEditing, setIsEditing] = React.useState(false);
const [fields, setFields] = useState<MasspayPaymentFields[]>([]);
const dispatch = useAppDispatch();
const { data: withdrawlOptions, isLoading: loadingWithdrawlOptions } = useGetMassPayPaymentMethodsQuery();
const [getMassPayFields, { isLoading: gettingFields }] = useGetMassPayPaymentFieldsMutation();
const handleTypeChange = (value: string) => {
formik.setFieldValue("type", value);
formik.setFieldValue("payment_fields", {});
setFields([]);
};
React.useEffect(() => {
if (open) {
formik.setFieldValue("wallet_address", wallet);
setIsEditing(false);
const handleContinueWithdrawl = async () => {
if (!formik.values.type) {
dispatch(
showToast({
message: "Please select a payment method",
variant: ToastVariant.ERROR
})
);
return;
}
}, [open, wallet]);
const handleChangeAddress = () => {
setIsEditing(true);
formik.setFieldValue("wallet_address", "");
try {
const response = await getMassPayFields({ token: formik.values.type }).unwrap();
const fetchedFields = response?.data || [];
setFields(fetchedFields);
formik.setFieldValue(
"payment_fields",
fetchedFields.map((item) => ({
...item,
value: item.value || "",
}))
);
} catch (e: any) {
dispatch(
showToast({
message: e?.data?.message || "Failed to get payment fields. Please try again.",
variant: ToastVariant.ERROR
})
);
}
};
const handleBackToPaymentMethods = () => {
setFields([]);
formik.setFieldValue("payment_fields", {});
};
const handleTypeChange = (value: string) => {
formik.setFieldValue("type", value)
handleChangeAddress();
}
return (
<Modal open={open} onClose={handleClose}>
<Box
......@@ -45,13 +99,15 @@ export default function WithdrawlModal({
left: "50%",
transform: "translate(-50%, -50%)",
borderRadius: "24px",
maxWidth: "574px",
maxWidth: "992px",
width: "100%",
background:
"linear-gradient(0deg, rgba(255, 255, 255, 0.04) 0%, rgba(255, 255, 255, 0.04) 100%), #3A013F",
boxShadow: 24,
p: { xs: 3, sm: 4 },
textAlign: "center",
overflow: "auto",
maxHeight: "90vh"
}}
>
{/* Wallet Banner */}
......@@ -69,105 +125,86 @@ export default function WithdrawlModal({
</span>
<h1 className="text-[24px] leading-[120%] font-[700]">
Confirm your Wallet Address
{fields.length > 0 ? "Enter Payment Details" : "Confirm your Wallet Address"}
</h1>
<p className="text-[11px] leading-[150%] text-center max-w-[420px] mx-auto mt-3 mb-6">
Your Withdrawn amount will be sent to the following address.
{fields.length > 0
? "Please fill in all the required information to complete your withdrawal."
: "Your Withdrawn amount will be sent to the following address."}
</p>
<form onSubmit={formik.handleSubmit} className="flex flex-col gap-3">
<div className="grid sm:grid-cols-2 mb-8 gap-6">
<div className="col-span-1">
<GlassWrapper>
<div className="py-5 px-4 flex justify-between items-center cursor-pointer" onClick={() => handleTypeChange("tryspeed")} >
<span className="text-[14px] flex items-center justify-start gap-2"><BitCoinIcon />Try Speed</span>
{formik.values.type === "tryspeed" ? <TickCircle /> : ""}
</div>
</GlassWrapper>
</div>
<div className="col-span-1">
<GlassWrapper>
<div className="py-5 px-4 flex justify-between items-center cursor-pointer" onClick={() => handleTypeChange("masspay")}>
<span className="text-[14px] flex items-center justify-start gap-2"><BitcoinRefresh />Masspay</span>
{formik.values.type === "masspay" ? <TickCircle /> : ""}
</div>
</GlassWrapper>
</div>
</div>
{formik.values.type === "tryspeed" ?
<div className="relative">
<InputLabel htmlFor="photoid_number" className="text-start">Photo ID <span className="text-red-500">*</span></InputLabel>
<OutlinedInput
name="photoid_number"
id="photoid_number"
value={formik.values.photoid_number}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
placeholder="Enter your Photo ID"
/>
{
formik.touched.photoid_number && formik.errors.photoid_number ?
<span className="error text-start">{formik.errors.photoid_number || ""}</span> : null
}
</div> : ""}
<div className="relative">
<InputLabel htmlFor="wallet_address" className="text-start"> {formik.values.type === "masspay" ? "Wallet Address" : "Lightining Address"}<span className="text-red-500">*</span></InputLabel>
<div className="relative">
<OutlinedInput
name="wallet_address"
id="wallet_address"
value={formik.values.wallet_address}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
placeholder="Enter your bitcoin address"
disabled={!isEditing} // ✅ locked until change
/>
{!isEditing && (
<form onSubmit={formik.handleSubmit} className="flex flex-col gap-3 h-full overflow-auto">
{fields.length > 0 ? (
<>
<div className="flex flex-col md:grid grid-cols-2 gap-4">
{fields.map((field) => (
<div className={field.type === "IDSelfieCollection" ? "col-span-2" : "col-span-1"} key={field.token}>
{field.type === "IDSelfieCollection" ? <Link href={field.value} className="bg-primary-grad ss-btn">{field.label}</Link> : <RenderFields field={field} formik={formik} />}
</div>
))}
</div>
<div className="flex gap-3 mt-4">
<Button
className="!p-0 !text-white"
sx={{
position: "absolute",
top: "50%",
transform: "translateY(-50%)",
right: 16,
maxWidth: "fit-content",
textDecoration: "underline",
}}
variant="outlined"
color="secondary"
fullWidth
onClick={handleBackToPaymentMethods}
type="button"
onClick={handleChangeAddress}
>
| &nbsp;&nbsp;Change Address
Back
</Button>
)}
</div>
</div>
{formik.values.type === "masspay" ? <div className="relative">
<InputLabel htmlFor="ssn" className="text-start">SSN <span className="text-red-500">*</span></InputLabel>
<OutlinedInput
name="ssn"
id="ssn"
value={formik.values.ssn}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
placeholder="Enter your Photo ID"
/>
{
formik.touched.ssn && formik.errors.ssn ?
<span className="error text-start">{formik.errors.ssn || ""}</span> : null
}
</div> : ""}
<Button
type="submit"
variant="contained"
color="primary"
className="!mt-3"
disabled={!formik.values.wallet_address}
>
Withdraw Now
</Button>
<Button
variant="contained"
color="primary"
fullWidth
type="submit"
>
{isLoading ? "Processing..." : "Withdraw Now"}
</Button>
</div>
</>
) : (
<>
<div className="grid sm:grid-cols-2 md:grid-cols-3 mb-8 gap-6">
{loadingWithdrawlOptions && (
<>
<ShimmerCard />
<ShimmerCard />
</>
)}
{!loadingWithdrawlOptions && withdrawlOptions?.data && withdrawlOptions?.data?.length > 0 &&
withdrawlOptions?.data?.map((option) => (
<div className="col-span-1" key={option?.id}>
<GlassWrapper>
<div
className="py-5 px-4 flex justify-between items-center cursor-pointer transition-all hover:bg-white/5"
onClick={() => handleTypeChange(option?.destination_token)}
>
<span className="text-[12px] flex items-center justify-start gap-2 max-w-[80%] text-start">
<BitcoinRefresh />
{option?.name}
</span>
{formik.values.type === option?.destination_token ? (
<TickCircle className="text-green-400" />
) : ""}
</div>
</GlassWrapper>
</div>
))
}
</div>
<Button
variant="contained"
color="primary"
className="!mt-3"
onClick={handleContinueWithdrawl}
disabled={!formik.values.type || gettingFields}
>
{gettingFields ? "Loading Fields..." : "Continue Withdrawal"}
</Button>
</>
)}
</form>
{/* Powered by */}
......@@ -180,5 +217,4 @@ export default function WithdrawlModal({
</Box>
</Modal>
);
}
}
\ No newline at end of file
"use client";
import { useAppDispatch, useAppSelector } from "@/hooks/hook";
import { useWithdrawlMutation } from "@/services/transaction";
import { useAppDispatch } from "@/hooks/hook";
import { useSubmitMassPayPaymentFieldsMutation } from "@/services/transaction";
import { showToast, ToastVariant } from "@/slice/toastSlice";
import { openPasswordDialog } from "@/slice/updatePasswordSlice";
import { GameResponseProps } from "@/types/game";
import { MasspayPaymentFields } from "@/types/transaction";
import { Button, OutlinedInput } from "@mui/material";
import { CardPos } from "@wandersonalwes/iconsax-react";
import { useFormik } from "formik";
......@@ -30,10 +30,30 @@ const validationSchema = Yup.object({
export type WithdrawlFormValues = {
game_provider: string;
withdrawl_amounts: Record<string, number | "">;
wallet_address: string;
photoid_number: string;
ssn: string;
type: "tryspeed" | "masspay"
type: string;
payment_fields: MasspayPaymentFields[];
};
export const validateDynamicField = (
field: MasspayPaymentFields,
value?: string
): string | undefined => {
if (!value || value.trim() === "") {
return `${field.label} is required`;
}
if (field.validation && field.input_type !== "options") {
try {
const regex = new RegExp(field.validation, "u");
if (!regex.test(value)) {
return `Invalid ${field.label}`;
}
} catch {
return `Invalid ${field.label}`;
}
}
return undefined;
};
export default function WithdrawlPage({
......@@ -44,38 +64,66 @@ export default function WithdrawlPage({
coins: any;
}) {
const [open, setOpen] = React.useState(false);
const user = useAppSelector((state) => state.auth.user);
const gameInfo = coins?.data?.game_information || {};
const dispatch = useAppDispatch();
const [withdrawMoney] =
useWithdrawlMutation();
const [withdrawMoney, { isLoading }] = useSubmitMassPayPaymentFieldsMutation();
const formik = useFormik<WithdrawlFormValues>({
initialValues: {
game_provider: "",
withdrawl_amounts: {},
wallet_address: user?.wallet_address || "",
photoid_number: "",
type: "tryspeed",
ssn: ""
type: "",
payment_fields: []
},
validationSchema,
enableReinitialize: true,
onSubmit: async (values) => {
try {
const amount =
values.withdrawl_amounts[values.game_provider];
const response = await withdrawMoney({
wallet: values.wallet_address,
const amount = values.withdrawl_amounts[values.game_provider];
const fieldErrors: Record<string, string> = {};
let hasErrors = false;
values.payment_fields.forEach((field) => {
const error = validateDynamicField(field, field.value);
if (error) {
fieldErrors[field.token] = error;
hasErrors = true;
}
});
if (hasErrors) {
// Fixed: Set errors as object, not array
formik.setErrors({
...formik.errors,
payment_fields: fieldErrors as any
});
dispatch(
showToast({
message: "Please fill in all required fields correctly",
variant: ToastVariant.ERROR,
})
);
return;
}
const payload: any = {
amount: Number(amount),
game_provider: values.game_provider,
photoid_number: values.photoid_number,
ssn: values.ssn,
type: values.type
}).unwrap();
};
if (values.type && formik.values.payment_fields.length > 0) {
payload.values = formik.values.payment_fields;
}
const response = await withdrawMoney({ token: values.type, body: payload }).unwrap();
setOpen(false);
formik.resetForm();
dispatch(
showToast({
message: response?.message || "Withdraw request submitted successfully!",
......@@ -83,7 +131,7 @@ export default function WithdrawlPage({
})
);
} catch (e: any) {
console.log(e);
console.log("Withdrawal Error:", e);
dispatch(
showToast({
message: e?.data?.message || "Something went wrong",
......@@ -93,11 +141,9 @@ export default function WithdrawlPage({
}
},
});
console.log("Formik Values:", formik.values, formik.errors);
const handleWithdrawlChange = (
provider: string,
value: string
) => {
const handleWithdrawlChange = (provider: string, value: string) => {
if (value === "") {
formik.setFieldValue(`withdrawl_amounts.${provider}`, "");
} else {
......@@ -109,15 +155,11 @@ export default function WithdrawlPage({
}
};
const handleWithdrawClick = (
balance: number,
provider: string
) => {
const handleWithdrawClick = (balance: number, provider: string) => {
if (balance < 2) {
dispatch(
showToast({
message:
"Insufficient balance to withdraw (Min $2 required)",
message: "Insufficient balance to withdraw (Min $2 required)",
variant: ToastVariant.ERROR,
})
);
......@@ -127,16 +169,26 @@ export default function WithdrawlPage({
setOpen(true);
};
const handleModalClose = () => {
setOpen(false);
formik.setFieldValue("payment_fields", []);
formik.setFieldValue("type", "");
if (formik.errors.payment_fields) {
const newErrors = { ...formik.errors };
delete newErrors.payment_fields;
formik.setErrors(newErrors);
}
};
console.log("Formik Errors:", formik.values.withdrawl_amounts);
return (
<section className="withdrawl__root">
<div className="section__title mb-4 lg:mb-8 max-w-[520px]">
<h1 className="mb-2 text-[24px] lg:text-[32px]">
Withdraw Coins
</h1>
<h1 className="mb-2 text-[24px] lg:text-[32px]">Withdraw Coins</h1>
<p className="text-[11px] lg:text-[13px]">
To start playing and cashing out your winnings, you’ll
need a crypto wallet to purchase E-Credits and receive
payouts. Don't worry—it’s quick and easy!
To start playing and cashing out your winnings, you'll need a crypto
wallet to purchase E-Credits and receive payouts. Don't worry—it's quick
and easy!
</p>
</div>
......@@ -145,11 +197,10 @@ export default function WithdrawlPage({
{games?.data?.data
?.filter((game) => game.provider.toLowerCase() !== "goldcoincity")
.map((game) => {
const info =
gameInfo[game.provider.toLowerCase()] || {
available: 0,
type: "sc",
};
const info = gameInfo[game.provider.toLowerCase()] || {
available: 0,
type: "sc",
};
return (
<div
......@@ -163,10 +214,7 @@ export default function WithdrawlPage({
{/* Game Info */}
<div className="flex gap-4 items-center mb-4 lg:col-span-1">
<Image
src={
game.thumbnail ||
"/assets/images/fallback.png"
}
src={game.thumbnail || "/assets/images/fallback.png"}
alt={game.name}
width={66}
height={66}
......@@ -195,9 +243,8 @@ export default function WithdrawlPage({
id={`withdrawl-${game.provider}`}
type="number"
value={
formik.values.withdrawl_amounts[
game.provider
] ?? ""
formik.values.withdrawl_amounts[game.provider] ??
""
}
onChange={(e) =>
handleWithdrawlChange(
......@@ -244,9 +291,7 @@ export default function WithdrawlPage({
}
</span>
)}
<span className="text-[8px] lg:text-[10px]">
Min $2.0
</span>
<span className="text-[8px] lg:text-[10px]">Min $2.0</span>
</div>
{/* Withdraw Button */}
......@@ -271,11 +316,9 @@ export default function WithdrawlPage({
] || 0
),
game.provider
)
);
}
}
}
}}
type="button"
>
Withdraw
......@@ -288,12 +331,7 @@ export default function WithdrawlPage({
</div>
</form>
<WithdrawlModal
open={open}
handleClose={() => setOpen(false)}
formik={formik}
wallet={user?.wallet_address || ""}
/>
</section >
<WithdrawlModal open={open} handleClose={handleModalClose} formik={formik} isLoading={isLoading} />
</section>
);
}
}
\ No newline at end of file
import { MasspayPaymentFields } from "@/types/transaction";
import { FormControl, FormHelperText, InputLabel, MenuItem, OutlinedInput, Select } from "@mui/material";
import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers";
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
import dayjs, { Dayjs } from "dayjs";
import { FormikProps } from "formik";
import { validateDynamicField } from ".";
interface RenderFieldsProps {
field: MasspayPaymentFields;
formik: FormikProps<any>;
}
export const RenderFields = ({ field, formik }: RenderFieldsProps) => {
// Get the actual field object from payment_fields array
const fieldIndex = formik.values.payment_fields?.findIndex(
(f: MasspayPaymentFields) => f.token === field.token
);
const fieldValue = fieldIndex !== -1
? formik.values.payment_fields[fieldIndex]?.value || ""
: "";
// Get error from the payment_fields error object
const errorFields = formik.errors.payment_fields as Record<string, string> | undefined;
const fieldError = errorFields?.[field.token];
const handleChange = (value: string | Dayjs | null) => {
let formattedValue = value;
if (field.input_type === "date" && value) {
formattedValue = dayjs(value as Dayjs).format("YYYY-MM-DD");
}
// Update the value in the payment_fields array
if (fieldIndex !== -1) {
const updatedFields = [...formik.values.payment_fields];
updatedFields[fieldIndex] = {
...updatedFields[fieldIndex],
value: formattedValue as string
};
formik.setFieldValue('payment_fields', updatedFields);
}
// Clear error for this field if it exists
if (errorFields?.[field.token]) {
const newErrors = { ...errorFields };
delete newErrors[field.token];
formik.setErrors({
...formik.errors,
payment_fields: Object.keys(newErrors).length > 0 ? newErrors : undefined
});
}
};
const handleBlur = () => {
const currentField = formik.values.payment_fields[fieldIndex];
const error = validateDynamicField(currentField, currentField?.value);
if (error) {
formik.setErrors({
...formik.errors,
payment_fields: {
...(formik.errors.payment_fields as Record<string, string> || {}),
[field.token]: error
}
});
}
};
const getOptions = (): string[] => {
if (field.input_type === "options" && field.validation) {
return field.validation.split("|");
}
return [];
};
switch (field.input_type) {
case "text":
return (
<div className="input__field text-left">
<InputLabel>{field.label} <span className="text-red-500">*</span></InputLabel>
<OutlinedInput
fullWidth
id={field.token}
name={`payment_fields.${field.token}`}
value={fieldValue}
onChange={(e) => handleChange(e.target.value)}
onBlur={handleBlur}
error={Boolean(fieldError)}
placeholder={field.expected_value}
/>
{fieldError && (
<FormHelperText error>{fieldError}</FormHelperText>
)}
</div>
);
case "options":
const options = getOptions();
return (
<div className="input__field text-left">
<InputLabel>{field.label} <span className="text-red-500">*</span></InputLabel>
<FormControl fullWidth error={Boolean(fieldError)}>
<Select
id={field.token}
name={`payment_fields.${field.token}`}
value={fieldValue}
onChange={(e) => handleChange(e.target.value)}
onBlur={handleBlur}
displayEmpty
>
<MenuItem value="" disabled>
Select {field.label}
</MenuItem>
{options.map((option) => (
<MenuItem key={option} value={option}>
{option}
</MenuItem>
))}
</Select>
{fieldError && (
<FormHelperText>{fieldError}</FormHelperText>
)}
</FormControl>
</div>
);
case "date":
return (
<div className="input__field text-left">
<InputLabel>{field.label} <span className="text-red-500">*</span></InputLabel>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DatePicker
value={fieldValue ? dayjs(fieldValue) : null}
onChange={(newValue) => handleChange(newValue)}
onClose={handleBlur}
slotProps={{
textField: {
fullWidth: true,
id: field.token,
name: `payment_fields.${field.token}`,
placeholder: field.expected_value || "YYYY-MM-DD",
error: Boolean(fieldError),
onBlur: handleBlur,
helperText: fieldError || "",
sx: {
'& .MuiOutlinedInput-root, & .MuiPickersInputBase-root, & .MuiPickersOutlinedInput-root': {
borderRadius: '27px',
background: 'rgba(118, 107, 120, 0.55)',
color: '#fff',
'& .MuiOutlinedInput-notchedOutline, & .MuiPickersOutlinedInput-notchedOutline': {
border: '0.576px solid rgba(255, 255, 255, 0.04)',
},
'&:hover .MuiOutlinedInput-notchedOutline, &:hover .MuiPickersOutlinedInput-notchedOutline': {
borderColor: 'rgba(255,255,255,0.2)',
},
'&.Mui-focused .MuiOutlinedInput-notchedOutline, &.Mui-focused .MuiPickersOutlinedInput-notchedOutline': {
borderColor: '#B801C0',
},
},
'& .MuiOutlinedInput-input, & .MuiPickersInputBase-input': {
padding: '12px 16px',
color: '#fff',
'&::placeholder': {
color: 'rgba(255, 255, 255, 0.2)',
fontWeight: 300,
fontSize: '12px',
opacity: 1,
},
},
'& .MuiInputAdornment-root': {
marginRight: '8px',
},
'& .MuiInputAdornment-root button': {
color: 'rgba(255, 255, 255, 0.7)',
'&:hover': {
color: '#fff',
background: 'rgba(255, 255, 255, 0.08)',
}
},
'& .MuiIconButton-root': {
padding: '8px',
}
}
},
popper: {
sx: {
"& .MuiPickersCalendarHeader-label": {
color: "#fff",
},
"& .MuiDayCalendar-weekDayLabel": {
color: "#fff",
},
"& .MuiPickersDay-root": {
color: "#fff",
},
"& .MuiPickersDay-root.Mui-selected": {
backgroundColor: "#B801C0",
},
"& .MuiPickersDay-root:hover": {
backgroundColor: "rgba(184, 1, 192, 0.3)",
},
"& .MuiPickersArrowSwitcher-button": {
color: "#fff",
},
"& .MuiPickersCalendarHeader-root": {
color: "#fff",
},
"& .MuiPickersDay-root.MuiPickersDay-today": {
backgroundColor: "#B801C0",
border: "1px solid #fff",
"&:not(.Mui-selected)": {
backgroundColor: "#B801C0",
},
},
},
},
}}
maxDate={dayjs()}
format="YYYY-MM-DD"
/>
</LocalizationProvider>
</div>
);
default:
return (
<div className="input__field text-left">
<InputLabel>{field.label} <span className="text-red-500">*</span></InputLabel>
<OutlinedInput
fullWidth
id={field.token}
name={`payment_fields.${field.token}`}
value={fieldValue}
onChange={(e) => handleChange(e.target.value)}
onBlur={handleBlur}
error={Boolean(fieldError)}
placeholder={field.expected_value}
/>
{fieldError && (
<FormHelperText error>{fieldError}</FormHelperText>
)}
</div>
);
}
};
\ No newline at end of file
import { TransactionStatusProps } from "@/components/pages/dashboard/adminDashboard/transaction/TransactionTable";
import { QueryParams } from "@/types/config";
import { DepositListProps, DepositProps, DepositResponseProps } from "@/types/transaction";
import { GlobalResponse, QueryParams } from "@/types/config";
import { DepositListProps, DepositProps, DepositResponseProps, MasspayPaymentFields, MasspayPaymentMethods } from "@/types/transaction";
import { createApi } from "@reduxjs/toolkit/query/react";
import { baseQuery } from "./baseQuery";
// Define proper request/response types
interface SubmitMassPayRequest {
token: string;
body: {
amount: number;
game_provider: string;
values?: { token: string; value: string }[];
};
}
interface MassPayFieldsResponse {
data: MasspayPaymentFields[];
success: boolean;
message: string;
}
interface MassPayMethodsResponse {
data: MasspayPaymentMethods[];
success: boolean;
message: string;
}
export const transactionApi = createApi({
reducerPath: "transactionApi",
baseQuery: baseQuery,
......@@ -81,7 +103,36 @@ export const transactionApi = createApi({
},
providesTags: ["Withdrawl", "Deposit"]
}),
getMassPayPaymentMethods: builder.query<MassPayMethodsResponse, void>({
query: () => ({
url: `/api/payment`,
method: "GET"
})
}),
getMassPayPaymentFields: builder.mutation<MassPayFieldsResponse, { token: string }>({
query: ({ token }) => ({
url: `/api/payment/fields?token=${token}`,
method: "GET"
})
}),
submitMassPayPaymentFields: builder.mutation<GlobalResponse, SubmitMassPayRequest>({
query: ({ token, body }) => ({
url: `/api/payment/fields?token=${token}`,
method: "POST",
body
}),
invalidatesTags: ["Withdrawl"]
}),
})
})
export const { useDepositMutation, useGetAllDepositQuery, useWithdrawlMutation, useGetAllWithdrawlQuery, useGetAllTransactionQuery } = transactionApi;
\ No newline at end of file
export const {
useDepositMutation,
useGetAllDepositQuery,
useWithdrawlMutation,
useGetAllWithdrawlQuery,
useGetAllTransactionQuery,
useGetMassPayPaymentFieldsMutation,
useGetMassPayPaymentMethodsQuery,
useSubmitMassPayPaymentFieldsMutation
} = transactionApi;
\ No newline at end of file
......@@ -14,8 +14,8 @@ export interface DepositUrlProps {
amount: number;
currency: string;
payment_url: string;
merchant_id:string;
merchant_id: string;
}
......@@ -47,4 +47,23 @@ export interface DepositListProps {
}
success: boolean;
message: string;
}
export interface MasspayPaymentMethods {
id: number;
name: string;
destination_token: string;
fee: number;
}
export interface MasspayPaymentFields {
input_type: "text" | "options" | "date";
token: string;
is_optional: boolean;
is_required: boolean;
label: string;
validation: string;
type: "BillReferenceNumber" | "BankAccountType" | "BankAccountNumber" | "BankRoutingNumber" | "SocialSecurity" | "DateOfBirth" | "Address1" | "IDSelfieCollection";
expected_value: string;
value: string;
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment