Commit 3fd08b1b by Arjun Jhukal

Merge branch 'dev'

parents 56221569 76a219fb
...@@ -10,6 +10,7 @@ function LayoutContent({ children }: { children: React.ReactNode }) { ...@@ -10,6 +10,7 @@ function LayoutContent({ children }: { children: React.ReactNode }) {
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const visitorId = searchParams.get("visitor_id"); const visitorId = searchParams.get("visitor_id");
useEffect(() => { useEffect(() => {
if (visitorId) { if (visitorId) {
localStorage.setItem("visitor_id", visitorId); localStorage.setItem("visitor_id", visitorId);
......
...@@ -7,13 +7,11 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> { ...@@ -7,13 +7,11 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const frontendUrl = process.env.NEXT_PUBLIC_FRONTEND_URL!; const frontendUrl = process.env.NEXT_PUBLIC_FRONTEND_URL!;
const apiUrl = process.env.NEXT_PUBLIC_BASE_URL!; const apiUrl = process.env.NEXT_PUBLIC_BASE_URL!;
// ✅ Fetch Menus
const menuRes = await fetch(`${apiUrl}/api/general/menus`, { const menuRes = await fetch(`${apiUrl}/api/general/menus`, {
next: { revalidate: 48600 }, next: { revalidate: 48600 },
}); });
const menuData = await menuRes.json(); const menuData = await menuRes.json();
// ✅ Fetch Games
const gameRes = await getAllGames(); const gameRes = await getAllGames();
const gameData = gameRes?.data?.data || []; const gameData = gameRes?.data?.data || [];
...@@ -21,17 +19,12 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> { ...@@ -21,17 +19,12 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
{ {
url: frontendUrl, url: frontendUrl,
priority: 1 priority: 1
// lastModified: new Date(),
// changeFrequency: "monthly",
}, },
]; ];
// ✅ Append /general/[slug]
if (menuData?.data?.length) { if (menuData?.data?.length) {
const menuUrls: MetadataRoute.Sitemap = menuData.data.map((menu: any) => ({ const menuUrls: MetadataRoute.Sitemap = menuData.data.map((menu: any) => ({
url: `${frontendUrl}/general/${menu.slug}`, url: `${frontendUrl}/general/${menu.slug}`,
// lastModified: new Date(),
// changeFrequency: "weekly",
priority: 0.9 priority: 0.9
})); }));
...@@ -42,11 +35,10 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> { ...@@ -42,11 +35,10 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
if (gameData.length) { if (gameData.length) {
const gameUrls: MetadataRoute.Sitemap = gameData.map((game: any) => ({ const gameUrls: MetadataRoute.Sitemap = gameData.map((game: any) => ({
url: `${frontendUrl}/exclusive-games/${game.id}`, url: `${frontendUrl}/exclusive-games/${game.id}`,
// lastModified: new Date(),
// changeFrequency: "weekly",
priority: 0.9 priority: 0.9
})); }));
urls.push(...gameUrls); 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"; "use client";
import GlassWrapper from '@/components/molecules/GlassWrapper'; import GlassWrapper from '@/components/molecules/GlassWrapper';
...@@ -23,7 +201,6 @@ export default function CheckoutPage({ amount, slug, bonus }: { ...@@ -23,7 +201,6 @@ export default function CheckoutPage({ amount, slug, bonus }: {
const [getPaymentLink, { isLoading: gettingLink }] = useDepositMutation(); const [getPaymentLink, { isLoading: gettingLink }] = useDepositMutation();
const [currentPaymentMode, setCurrentPaymentMode] = React.useState("crypto"); const [currentPaymentMode, setCurrentPaymentMode] = React.useState("crypto");
// console.log(pathname)
return ( return (
<section className="checkout__root"> <section className="checkout__root">
<div className="grid grid-cols-12 gap-4 lg:gap-10 xl:gap-12"> <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 }: { ...@@ -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> <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> <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="grid sm:grid-cols-2 mb-8 gap-6">
<div className="col-span-1"> <div className="col-span-1">
<GlassWrapper> <GlassWrapper>
......
"use client";
import GlassWrapper from "@/components/molecules/GlassWrapper"; import GlassWrapper from "@/components/molecules/GlassWrapper";
import BitCoinIcon from "@/icons/BitCoinIcon"; import { useAppDispatch } from "@/hooks/hook";
import { Box, Button, InputLabel, Modal, OutlinedInput } from "@mui/material"; 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 { BitcoinRefresh, SecuritySafe, TickCircle } from "@wandersonalwes/iconsax-react";
import { FormikProps } from "formik"; import { FormikProps } from "formik";
import Image from "next/image"; import Image from "next/image";
import React from "react"; import Link from "next/link";
import { useState } from "react";
import { WithdrawlFormValues } from "."; 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({ export default function WithdrawlModal({
open, open,
handleClose, handleClose,
formik, formik,
wallet isLoading
}: { }: {
open: boolean; open: boolean;
handleClose: () => void; handleClose: () => void;
formik: FormikProps<WithdrawlFormValues>; 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(() => { const handleContinueWithdrawl = async () => {
if (open) { if (!formik.values.type) {
formik.setFieldValue("wallet_address", wallet); dispatch(
setIsEditing(false); showToast({
message: "Please select a payment method",
variant: ToastVariant.ERROR
})
);
return;
} }
}, [open, wallet]);
const handleChangeAddress = () => { try {
setIsEditing(true); const response = await getMassPayFields({ token: formik.values.type }).unwrap();
formik.setFieldValue("wallet_address", ""); 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 ( return (
<Modal open={open} onClose={handleClose}> <Modal open={open} onClose={handleClose}>
<Box <Box
...@@ -45,13 +99,15 @@ export default function WithdrawlModal({ ...@@ -45,13 +99,15 @@ export default function WithdrawlModal({
left: "50%", left: "50%",
transform: "translate(-50%, -50%)", transform: "translate(-50%, -50%)",
borderRadius: "24px", borderRadius: "24px",
maxWidth: "574px", maxWidth: "992px",
width: "100%", width: "100%",
background: background:
"linear-gradient(0deg, rgba(255, 255, 255, 0.04) 0%, rgba(255, 255, 255, 0.04) 100%), #3A013F", "linear-gradient(0deg, rgba(255, 255, 255, 0.04) 0%, rgba(255, 255, 255, 0.04) 100%), #3A013F",
boxShadow: 24, boxShadow: 24,
p: { xs: 3, sm: 4 }, p: { xs: 3, sm: 4 },
textAlign: "center", textAlign: "center",
overflow: "auto",
maxHeight: "90vh"
}} }}
> >
{/* Wallet Banner */} {/* Wallet Banner */}
...@@ -69,105 +125,86 @@ export default function WithdrawlModal({ ...@@ -69,105 +125,86 @@ export default function WithdrawlModal({
</span> </span>
<h1 className="text-[24px] leading-[120%] font-[700]"> <h1 className="text-[24px] leading-[120%] font-[700]">
Confirm your Wallet Address {fields.length > 0 ? "Enter Payment Details" : "Confirm your Wallet Address"}
</h1> </h1>
<p className="text-[11px] leading-[150%] text-center max-w-[420px] mx-auto mt-3 mb-6"> <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> </p>
<form onSubmit={formik.handleSubmit} className="flex flex-col gap-3"> <form onSubmit={formik.handleSubmit} className="flex flex-col gap-3 h-full overflow-auto">
<div className="grid sm:grid-cols-2 mb-8 gap-6"> {fields.length > 0 ? (
<div className="col-span-1"> <>
<GlassWrapper> <div className="flex flex-col md:grid grid-cols-2 gap-4">
<div className="py-5 px-4 flex justify-between items-center cursor-pointer" onClick={() => handleTypeChange("tryspeed")} > {fields.map((field) => (
<span className="text-[14px] flex items-center justify-start gap-2"><BitCoinIcon />Try Speed</span> <div className={field.type === "IDSelfieCollection" ? "col-span-2" : "col-span-1"} key={field.token}>
{formik.values.type === "tryspeed" ? <TickCircle /> : ""} {field.type === "IDSelfieCollection" ? <Link href={field.value} className="bg-primary-grad ss-btn">{field.label}</Link> : <RenderFields field={field} formik={formik} />}
</div> </div>
</GlassWrapper> ))}
</div> </div>
<div className="col-span-1"> <div className="flex gap-3 mt-4">
<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 && (
<Button <Button
className="!p-0 !text-white" variant="outlined"
sx={{ color="secondary"
position: "absolute", fullWidth
top: "50%", onClick={handleBackToPaymentMethods}
transform: "translateY(-50%)",
right: 16,
maxWidth: "fit-content",
textDecoration: "underline",
}}
type="button" type="button"
onClick={handleChangeAddress}
> >
| &nbsp;&nbsp;Change Address Back
</Button> </Button>
)} <Button
</div> variant="contained"
</div> color="primary"
{formik.values.type === "masspay" ? <div className="relative"> fullWidth
<InputLabel htmlFor="ssn" className="text-start">SSN <span className="text-red-500">*</span></InputLabel> type="submit"
<OutlinedInput >
name="ssn" {isLoading ? "Processing..." : "Withdraw Now"}
id="ssn" </Button>
value={formik.values.ssn} </div>
onChange={formik.handleChange} </>
onBlur={formik.handleBlur} ) : (
placeholder="Enter your Photo ID" <>
/> <div className="grid sm:grid-cols-2 md:grid-cols-3 mb-8 gap-6">
{ {loadingWithdrawlOptions && (
formik.touched.ssn && formik.errors.ssn ? <>
<span className="error text-start">{formik.errors.ssn || ""}</span> : null <ShimmerCard />
} <ShimmerCard />
</div> : ""} </>
)}
<Button {!loadingWithdrawlOptions && withdrawlOptions?.data && withdrawlOptions?.data?.length > 0 &&
type="submit" withdrawlOptions?.data?.map((option) => (
variant="contained" <div className="col-span-1" key={option?.id}>
color="primary" <GlassWrapper>
className="!mt-3" <div
disabled={!formik.values.wallet_address} className="py-5 px-4 flex justify-between items-center cursor-pointer transition-all hover:bg-white/5"
> onClick={() => handleTypeChange(option?.destination_token)}
Withdraw Now >
</Button> <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> </form>
{/* Powered by */} {/* Powered by */}
...@@ -180,5 +217,4 @@ export default function WithdrawlModal({ ...@@ -180,5 +217,4 @@ export default function WithdrawlModal({
</Box> </Box>
</Modal> </Modal>
); );
} }
\ No newline at end of file
"use client"; "use client";
import { useAppDispatch, useAppSelector } from "@/hooks/hook"; import { useAppDispatch } from "@/hooks/hook";
import { useWithdrawlMutation } from "@/services/transaction"; import { useSubmitMassPayPaymentFieldsMutation } from "@/services/transaction";
import { showToast, ToastVariant } from "@/slice/toastSlice"; import { showToast, ToastVariant } from "@/slice/toastSlice";
import { openPasswordDialog } from "@/slice/updatePasswordSlice"; import { openPasswordDialog } from "@/slice/updatePasswordSlice";
import { GameResponseProps } from "@/types/game"; import { GameResponseProps } from "@/types/game";
import { MasspayPaymentFields } from "@/types/transaction";
import { Button, OutlinedInput } from "@mui/material"; import { Button, OutlinedInput } from "@mui/material";
import { CardPos } from "@wandersonalwes/iconsax-react"; import { CardPos } from "@wandersonalwes/iconsax-react";
import { useFormik } from "formik"; import { useFormik } from "formik";
...@@ -30,10 +30,30 @@ const validationSchema = Yup.object({ ...@@ -30,10 +30,30 @@ const validationSchema = Yup.object({
export type WithdrawlFormValues = { export type WithdrawlFormValues = {
game_provider: string; game_provider: string;
withdrawl_amounts: Record<string, number | "">; withdrawl_amounts: Record<string, number | "">;
wallet_address: string; type: string;
photoid_number: string; payment_fields: MasspayPaymentFields[];
ssn: string; };
type: "tryspeed" | "masspay"
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({ export default function WithdrawlPage({
...@@ -44,38 +64,66 @@ export default function WithdrawlPage({ ...@@ -44,38 +64,66 @@ export default function WithdrawlPage({
coins: any; coins: any;
}) { }) {
const [open, setOpen] = React.useState(false); const [open, setOpen] = React.useState(false);
const user = useAppSelector((state) => state.auth.user);
const gameInfo = coins?.data?.game_information || {}; const gameInfo = coins?.data?.game_information || {};
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const [withdrawMoney] = const [withdrawMoney, { isLoading }] = useSubmitMassPayPaymentFieldsMutation();
useWithdrawlMutation();
const formik = useFormik<WithdrawlFormValues>({ const formik = useFormik<WithdrawlFormValues>({
initialValues: { initialValues: {
game_provider: "", game_provider: "",
withdrawl_amounts: {}, withdrawl_amounts: {},
wallet_address: user?.wallet_address || "", type: "",
photoid_number: "", payment_fields: []
type: "tryspeed",
ssn: ""
}, },
validationSchema, validationSchema,
enableReinitialize: true, enableReinitialize: true,
onSubmit: async (values) => { onSubmit: async (values) => {
try { try {
const amount = const amount = values.withdrawl_amounts[values.game_provider];
values.withdrawl_amounts[values.game_provider];
const response = await withdrawMoney({ const fieldErrors: Record<string, string> = {};
wallet: values.wallet_address, 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), amount: Number(amount),
game_provider: values.game_provider, game_provider: values.game_provider,
photoid_number: values.photoid_number, };
ssn: values.ssn,
type: values.type if (values.type && formik.values.payment_fields.length > 0) {
}).unwrap(); payload.values = formik.values.payment_fields;
}
const response = await withdrawMoney({ token: values.type, body: payload }).unwrap();
setOpen(false); setOpen(false);
formik.resetForm();
dispatch( dispatch(
showToast({ showToast({
message: response?.message || "Withdraw request submitted successfully!", message: response?.message || "Withdraw request submitted successfully!",
...@@ -83,7 +131,7 @@ export default function WithdrawlPage({ ...@@ -83,7 +131,7 @@ export default function WithdrawlPage({
}) })
); );
} catch (e: any) { } catch (e: any) {
console.log(e); console.log("Withdrawal Error:", e);
dispatch( dispatch(
showToast({ showToast({
message: e?.data?.message || "Something went wrong", message: e?.data?.message || "Something went wrong",
...@@ -93,11 +141,9 @@ export default function WithdrawlPage({ ...@@ -93,11 +141,9 @@ export default function WithdrawlPage({
} }
}, },
}); });
console.log("Formik Values:", formik.values, formik.errors);
const handleWithdrawlChange = ( const handleWithdrawlChange = (provider: string, value: string) => {
provider: string,
value: string
) => {
if (value === "") { if (value === "") {
formik.setFieldValue(`withdrawl_amounts.${provider}`, ""); formik.setFieldValue(`withdrawl_amounts.${provider}`, "");
} else { } else {
...@@ -109,15 +155,11 @@ export default function WithdrawlPage({ ...@@ -109,15 +155,11 @@ export default function WithdrawlPage({
} }
}; };
const handleWithdrawClick = ( const handleWithdrawClick = (balance: number, provider: string) => {
balance: number,
provider: string
) => {
if (balance < 2) { if (balance < 2) {
dispatch( dispatch(
showToast({ showToast({
message: message: "Insufficient balance to withdraw (Min $2 required)",
"Insufficient balance to withdraw (Min $2 required)",
variant: ToastVariant.ERROR, variant: ToastVariant.ERROR,
}) })
); );
...@@ -127,16 +169,26 @@ export default function WithdrawlPage({ ...@@ -127,16 +169,26 @@ export default function WithdrawlPage({
setOpen(true); 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 ( return (
<section className="withdrawl__root"> <section className="withdrawl__root">
<div className="section__title mb-4 lg:mb-8 max-w-[520px]"> <div className="section__title mb-4 lg:mb-8 max-w-[520px]">
<h1 className="mb-2 text-[24px] lg:text-[32px]"> <h1 className="mb-2 text-[24px] lg:text-[32px]">Withdraw Coins</h1>
Withdraw Coins
</h1>
<p className="text-[11px] lg:text-[13px]"> <p className="text-[11px] lg:text-[13px]">
To start playing and cashing out your winnings, you’ll To start playing and cashing out your winnings, you'll need a crypto
need a crypto wallet to purchase E-Credits and receive wallet to purchase E-Credits and receive payouts. Don't worry—it's quick
payouts. Don't worry—it’s quick and easy! and easy!
</p> </p>
</div> </div>
...@@ -145,11 +197,10 @@ export default function WithdrawlPage({ ...@@ -145,11 +197,10 @@ export default function WithdrawlPage({
{games?.data?.data {games?.data?.data
?.filter((game) => game.provider.toLowerCase() !== "goldcoincity") ?.filter((game) => game.provider.toLowerCase() !== "goldcoincity")
.map((game) => { .map((game) => {
const info = const info = gameInfo[game.provider.toLowerCase()] || {
gameInfo[game.provider.toLowerCase()] || { available: 0,
available: 0, type: "sc",
type: "sc", };
};
return ( return (
<div <div
...@@ -163,10 +214,7 @@ export default function WithdrawlPage({ ...@@ -163,10 +214,7 @@ export default function WithdrawlPage({
{/* Game Info */} {/* Game Info */}
<div className="flex gap-4 items-center mb-4 lg:col-span-1"> <div className="flex gap-4 items-center mb-4 lg:col-span-1">
<Image <Image
src={ src={game.thumbnail || "/assets/images/fallback.png"}
game.thumbnail ||
"/assets/images/fallback.png"
}
alt={game.name} alt={game.name}
width={66} width={66}
height={66} height={66}
...@@ -195,9 +243,8 @@ export default function WithdrawlPage({ ...@@ -195,9 +243,8 @@ export default function WithdrawlPage({
id={`withdrawl-${game.provider}`} id={`withdrawl-${game.provider}`}
type="number" type="number"
value={ value={
formik.values.withdrawl_amounts[ formik.values.withdrawl_amounts[game.provider] ??
game.provider ""
] ?? ""
} }
onChange={(e) => onChange={(e) =>
handleWithdrawlChange( handleWithdrawlChange(
...@@ -244,9 +291,7 @@ export default function WithdrawlPage({ ...@@ -244,9 +291,7 @@ export default function WithdrawlPage({
} }
</span> </span>
)} )}
<span className="text-[8px] lg:text-[10px]"> <span className="text-[8px] lg:text-[10px]">Min $2.0</span>
Min $2.0
</span>
</div> </div>
{/* Withdraw Button */} {/* Withdraw Button */}
...@@ -271,11 +316,9 @@ export default function WithdrawlPage({ ...@@ -271,11 +316,9 @@ export default function WithdrawlPage({
] || 0 ] || 0
), ),
game.provider game.provider
) );
} }
} }}
}
type="button" type="button"
> >
Withdraw Withdraw
...@@ -288,12 +331,7 @@ export default function WithdrawlPage({ ...@@ -288,12 +331,7 @@ export default function WithdrawlPage({
</div> </div>
</form> </form>
<WithdrawlModal <WithdrawlModal open={open} handleClose={handleModalClose} formik={formik} isLoading={isLoading} />
open={open} </section>
handleClose={() => setOpen(false)}
formik={formik}
wallet={user?.wallet_address || ""}
/>
</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 { TransactionStatusProps } from "@/components/pages/dashboard/adminDashboard/transaction/TransactionTable";
import { QueryParams } from "@/types/config"; import { GlobalResponse, QueryParams } from "@/types/config";
import { DepositListProps, DepositProps, DepositResponseProps } from "@/types/transaction"; import { DepositListProps, DepositProps, DepositResponseProps, MasspayPaymentFields, MasspayPaymentMethods } from "@/types/transaction";
import { createApi } from "@reduxjs/toolkit/query/react"; import { createApi } from "@reduxjs/toolkit/query/react";
import { baseQuery } from "./baseQuery"; 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({ export const transactionApi = createApi({
reducerPath: "transactionApi", reducerPath: "transactionApi",
baseQuery: baseQuery, baseQuery: baseQuery,
...@@ -81,7 +103,36 @@ export const transactionApi = createApi({ ...@@ -81,7 +103,36 @@ export const transactionApi = createApi({
}, },
providesTags: ["Withdrawl", "Deposit"] 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; export const {
\ No newline at end of file useDepositMutation,
useGetAllDepositQuery,
useWithdrawlMutation,
useGetAllWithdrawlQuery,
useGetAllTransactionQuery,
useGetMassPayPaymentFieldsMutation,
useGetMassPayPaymentMethodsQuery,
useSubmitMassPayPaymentFieldsMutation
} = transactionApi;
\ No newline at end of file
...@@ -14,8 +14,8 @@ export interface DepositUrlProps { ...@@ -14,8 +14,8 @@ export interface DepositUrlProps {
amount: number; amount: number;
currency: string; currency: string;
payment_url: string; payment_url: string;
merchant_id:string; merchant_id: string;
} }
...@@ -47,4 +47,23 @@ export interface DepositListProps { ...@@ -47,4 +47,23 @@ export interface DepositListProps {
} }
success: boolean; success: boolean;
message: string; 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