Commit a9434592 by Arjun Jhukal

updated the new changes to firekirin reduced the size of the registration,…

updated the new changes to firekirin reduced the size of the registration, included the fortpay fields inline, changed the verified status for user
parent f95a2964
......@@ -2,8 +2,8 @@
import Chatbot from '@/components/atom/ChatbotIcon';
import DashboardLayout from '@/components/layouts/DashboardLayout';
import Toast from '@/components/molecules/Toast';
import AgeVerificationModal from '@/components/organism/dialog';
import AgeGate from '@/components/organism/dialog/AgeGate';
import { useSearchParams } from 'next/navigation';
import React, { Suspense, useEffect } from 'react';
......@@ -22,8 +22,12 @@ function LayoutContent({ children }: { children: React.ReactNode }) {
<DashboardLayout>
{children}
<AgeVerificationModal />
<Chatbot />
<AgeGate />
<div className="fixed bottom-1 right-2 lg:bottom-2 lg:right-4 flex flex-col justify-end items-end z-[9999] gap-8">
<Chatbot />
<Toast />
</div>
{/* <AgeGate /> */}
</DashboardLayout>
)
......
import Toast from '@/components/molecules/Toast'
import UpdatePassword from '@/components/organism/UpdatePassword'
import { ThemeContextProvider } from '@/context/ThemeContext'
import { ClientProvider } from '@/hooks/ReduxProvider'
......@@ -11,7 +10,7 @@ export default function ProviderWrapper({ children }: { children: React.ReactNod
<ClientProvider>
<ThemeCustomization>
{children}
<Toast />
<UpdatePassword />
{/* <AgeChecker
apiKey="lwU8lOYysWXrIZaijSG3Hfcxmzc4DlS9"
......
......@@ -12,7 +12,7 @@ export default function Chatbot() {
return (
<Button
className="fixed! bottom-2 right-2 lg:bottom-4 lg:right-4 max-w-fit px-8!"
className=" max-w-fit px-8!"
variant="contained"
color="primary"
fullWidth
......
"use client";
import React from "react";
import { Snackbar, Alert, IconButton } from "@mui/material";
import { Alert, IconButton, Snackbar } from "@mui/material";
import { CloseCircle } from "@wandersonalwes/iconsax-react";
import React from "react";
import { closeToast } from "@/slice/toastSlice";
import { useAppDispatch, useAppSelector } from "@/hooks/hook";
import { closeToast } from "@/slice/toastSlice";
export default function Toast() {
const { variant, message, isActive, autoTimeout, duration } = useAppSelector(
......@@ -28,10 +28,9 @@ export default function Toast() {
return (
<Snackbar
open={isActive}
anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
onClose={() => dispatch(closeToast())}
autoHideDuration={autoTimeout ? duration || 3000 : null}
sx={{ zIndex: 9999 }}
sx={{ position: "relative" }}
>
<Alert
severity={currentVariant as "success" | "error" | "warning" | "info"}
......
"use client";
import GlassWrapper from '@/components/molecules/GlassWrapper';
import { useAppSelector } from '@/hooks/hook';
import EditIcon from '@/icons/EditIcon'
import EditIcon from '@/icons/EditIcon';
import { formatDateTime } from '@/utils/formatDateTime';
import Image from 'next/image'
import React from 'react'
import Image from 'next/image';
import AgeGate from '../dialog/AgeGate';
export default function UserProfileCard({ balance, loading }: { balance: any; loading?: boolean }) {
const user = useAppSelector(state => state?.auth.user);
......@@ -56,11 +56,16 @@ export default function UserProfileCard({ balance, loading }: { balance: any; lo
<div className="flex justify-center items-center gap-3">
<Image src={"/assets/images/current-balance.svg"} alt='' width={48} height={48} />
<div className="content mt-3 text-start">
<strong className="text-[12px] leading-[120%] font-[700] text-white block ">SC:{balance?.data?.current_balance}</strong>
<strong className="text-[12px] leading-[120%] font-[700] text-white block ">SC : {balance?.data?.current_balance.toFixed(2)}</strong>
<span className="text-white text-[9px]">Current Balance</span>
</div>
</div>
</div>
{/* <Button variant="contained" color="primary" fullWidth className='col-span-1 md:col-span-2 mt-2' >
Verify Account Now
</Button> */}
<AgeGate />
{/* <div className="col-span-2 flex flex-col sm:flex-row gap-2">
<div className="w-full rounded-[14px] p-4 lg:py-6 flex justify-center sm:block text-left sm:text-center gap-3" style={{ background: "rgba(191, 26, 198, 0.10)" }}>
<Image src={"/assets/images/deposit.svg"} alt='' width={48} height={48} className='sm:mx-auto' />
......
import Avatar from '@/components/atom/Avatar';
import { useAppDispatch, useAppSelector } from '@/hooks/hook';
import { PATH } from '@/routes/PATH';
import { useGetAgeGateUuidQuery } from '@/services/authApi';
import { useGetAgeGateUuidMutation } from '@/services/authApi';
import { clearTokens } from '@/slice/authSlice';
import { Box, ClickAwayListener, Fade, List, ListItem, ListItemButton, ListItemIcon, ListItemText, Paper, Popper } from '@mui/material';
import { ArrowDown2, Coin, Logout, MoneySend, Profile, TickCircle, Wallet2 } from "@wandersonalwes/iconsax-react";
import { Box, ClickAwayListener, Fade, List, ListItem, ListItemButton, ListItemIcon, ListItemText, Paper, Popper, Tooltip, Typography } from '@mui/material';
import { ArrowDown2, CloseCircle, Coin, Logout, MoneySend, Profile, TickCircle, Wallet2 } from "@wandersonalwes/iconsax-react";
import Link from 'next/link';
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
import React, { useRef, useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';
const avataur1 = '/assets/images/avatar-6.png';
......@@ -106,7 +106,25 @@ export default function ProfileBlock() {
setGlassStyle((prev) => ({ ...prev, opacity: 0 }));
};
const { data } = useGetAgeGateUuidQuery();
const [getAgeGateUuid] = useGetAgeGateUuidMutation();
const [isVerified, setIsVerified] = useState<boolean | null>(null);
useEffect(() => {
const fetchAgeStatus = async () => {
try {
const res = await getAgeGateUuid().unwrap();
setIsVerified(res?.data?.is_age_verified);
} catch (e) {
console.log(e)
// console.error("Failed to fetch age verification status", err);
setIsVerified(false);
}
};
fetchAgeStatus();
}, [getAgeGateUuid])
return (
<Box >
......@@ -121,21 +139,29 @@ export default function ProfileBlock() {
padding: 0
}}
>
<div className=' lg:flex items-center gap-1 relative'>
<Avatar alt="profile user" src={avataur1} />
{user?.role && user.role.toLowerCase() !== "user" ? <>
<div className=' hidden lg:block'>
<strong className='text-[14px] leading-[120%] font-bold text-text-title block mb-1 text-nowrap'>{user?.name}</strong>
<p className='text-[12px] text-left leading-[120%] font-[500] text-para-light text-nowrap'>
{user?.role || "User"}
</p>
<Tooltip title={isVerified ? "Verified" : "Not Verified"} placement="bottom">
<div className=' lg:flex items-center gap-1 relative'>
<Avatar alt="profile user" src={avataur1} />
{user?.role && user.role.toLowerCase() !== "user" ? <>
<div className=' hidden lg:block'>
<strong className='text-[14px] leading-[120%] font-bold text-text-title block mb-1 text-nowrap'>{user?.name}</strong>
<p className='text-[12px] text-left leading-[120%] font-[500] text-para-light text-nowrap'>
{user?.role || "User"}
</p>
</div>
<ArrowDown2 size={14} className='text-primary hidden lg:block' />
</> : ""}
<div className="absolute bottom-0 right-0">
{isVerified ? (
<TickCircle variant="Bold" size={14} className="text-green-600" />
) : (
<span className="flex items-center justify-center w-[14px] h-[14px] rounded-full bg-yellow-400 text-[10px] font-bold text-white">
!
</span>
)}
</div>
<ArrowDown2 size={14} className='text-primary hidden lg:block' />
</> : ""}
<div className="absolute bottom-0 right-0">
{data?.data?.is_age_verified ? <TickCircle variant='Bold' size={16} className="text-green-500" /> : <TickCircle variant='Bold' size={16} className="text-white" />}
</div>
</div>
</Tooltip>
</a>
<Popper
id={id}
......@@ -150,7 +176,7 @@ export default function ProfileBlock() {
<Paper
elevation={3}
sx={{
width: 215,
width: 315,
borderRadius: 2,
mt: 1,
}}
......@@ -203,6 +229,7 @@ export default function ProfileBlock() {
>
<ListItemIcon className="min-w-[30px] mr-1 group-hover:text-primary">{item.icon}</ListItemIcon>
<ListItemText primary={item.label} className='group-hover:text-primary' />
</Link> : <ListItemButton
href={item.href || ""}
onClick={item.onClick}
......@@ -255,6 +282,25 @@ export default function ProfileBlock() {
>
<ListItemIcon className="min-w-[30px] mr-1 group-hover:text-primary">{item.icon}</ListItemIcon>
<ListItemText primary={item.label} className='group-hover:text-primary' />
{item.label === "Profile" && (
<div
className={`status flex flex-nowrap items-center gap-1 ml-auto p-1.5 rounded
${isVerified
? "bg-green-600/40 text-green-600"
: "bg-red-600/40 text-red-600"
}`}
>
{isVerified ? (
<TickCircle variant="Bold" size={12} />
) : (
<CloseCircle variant="Bold" size={12} />
)}
<Typography className="text-[10px]!">
{isVerified ? "Verified" : "Not Verified"}
</Typography>
</div>
)}
</Link> :
<ListItemButton
href={item.href || ""}
......@@ -263,6 +309,7 @@ export default function ProfileBlock() {
>
<ListItemIcon className="min-w-[30px] mr-1 group-hover:text-primary">{item.icon}</ListItemIcon>
<ListItemText primary={item.label} className='group-hover:text-primary' />
</ListItemButton>}
</ListItem>
))}
......
"use client";
import { useGetAgeGateUuidQuery, useVerifyAgeGateMutation } from "@/services/authApi";
import { useCallback, useEffect } from "react";
import { useAppDispatch } from "@/hooks/hook";
import { useGetAgeGateUuidMutation, useVerifyAgeGateMutation } from "@/services/authApi";
import { showToast, ToastVariant } from "@/slice/toastSlice";
import { Button } from "@mui/material";
import { useCallback } from "react";
export default function AgeGate() {
const { data, isSuccess } = useGetAgeGateUuidQuery();
const [getAgeGateUuid, { isLoading }] = useGetAgeGateUuidMutation();
const [verifyAgeGate] = useVerifyAgeGateMutation();
const dispatch = useAppDispatch();
const handleSuccess = useCallback(async (uuid: string) => {
try {
await verifyAgeGate({ age_verify_uuid: uuid }).unwrap();
......@@ -15,46 +20,68 @@ export default function AgeGate() {
}
}, [verifyAgeGate]);
console.log("AgeGate data:", data?.data?.age_verify_uuid, "isSuccess:", isSuccess);
useEffect(() => {
if (!isSuccess || !data?.data?.age_verify_uuid) return;
if (data.data.is_age_verified) return;
const uuid = data.data.age_verify_uuid;
(window as any).AgeCheckerConfig = {
key: process.env.NEXT_PUBLIC_AGE_CHECKER_KEY,
mode: "manual",
autoload: true,
show_close: true,
onready: () => {
(window as any).AgeCheckerAPI.show(uuid);
},
onstatuschanged: (verification: { uuid: string; status: string }) => {
if (verification.status === "accepted") {
handleSuccess(verification.uuid);
const openAgeGate = async () => {
try {
const res = await getAgeGateUuid().unwrap();
const uuid = res?.data?.age_verify_uuid;
const verified = res?.data?.is_age_verified;
if (!uuid || verified) return;
(window as any).AgeCheckerConfig = {
key: process.env.NEXT_PUBLIC_AGE_CHECKER_KEY,
mode: "manual",
autoload: true,
show_close: true,
onready: () => {
(window as any).AgeCheckerAPI.show(uuid);
},
onstatuschanged: (verification: { uuid: string; status: string }) => {
if (verification.status === "accepted") {
handleSuccess(verification.uuid);
}
},
onpagehide: () => {
(window as any).AgeCheckerAPI.close();
}
},
onpagehide: () => {
(window as any).AgeCheckerAPI.close();
};
const existing = document.querySelector('script[src*="agechecker.net"]');
if (existing) {
(window as any).AgeCheckerAPI?.show(uuid);
return;
}
};
const existing = document.querySelector('script[src*="agechecker.net"]');
if (existing) {
(window as any).AgeCheckerAPI?.show(uuid);
return;
}
const script = document.createElement("script");
script.src = "https://cdn.agechecker.net/static/popup/v1/popup.js";
script.crossOrigin = "anonymous";
const script = document.createElement("script");
script.src = "https://cdn.agechecker.net/static/popup/v1/popup.js";
script.crossOrigin = "anonymous";
script.onerror = () => {
window.location.href = "https://agechecker.net/loaderror";
};
document.head.insertBefore(script, document.head.firstChild);
script.onerror = () => {
window.location.href = "https://agechecker.net/loaderror";Pp
};
}, [isSuccess, data, handleSuccess]);
document.head.insertBefore(script, document.head.firstChild);
} catch (err: any) {
dispatch(showToast({
message: err?.data?.message || "Failed to initiate age verification. Please try again.",
variant: ToastVariant.ERROR
}))
}
};
return null;
return (
<Button
variant="contained"
color="primary"
fullWidth
className="col-span-1 md:col-span-2 mt-2"
onClick={openAgeGate}
disabled={isLoading}
>
{isLoading ? "Loading..." : "Verify Account Now"}
</Button>
);
}
\ No newline at end of file
......@@ -6,16 +6,11 @@ import { useAppDispatch } from '@/hooks/hook';
import { PATH } from '@/routes/PATH';
import { useRegisterUserMutation } from '@/services/authApi';
import { showToast, ToastVariant } from '@/slice/toastSlice';
import { Autocomplete, Box, Checkbox, FormControlLabel, InputLabel, OutlinedInput, TextField } from '@mui/material';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { Box, Checkbox, FormControlLabel, InputLabel, OutlinedInput } from '@mui/material';
import { ArrowLeft } from '@wandersonalwes/iconsax-react';
import dayjs, { Dayjs } from 'dayjs';
import { useFormik } from 'formik';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import * as Yup from 'yup';
import AuthMessageBlock from '../authMessageBlock';
......@@ -65,11 +60,11 @@ const validationSchema = Yup.object().shape({
.email('Must be a valid email')
.max(255)
.required('Email is required'),
// displayName: Yup.string()
// .required("Display Name is required")
// .max(14, "Display Name must be less than 14 characters")
// .min(6, "Display Name must be at least 6 characters long")
// .matches(/^\S+$/, "Display Name cannot contain spaces"),
displayName: Yup.string()
.required("Display Name is required")
.max(14, "Display Name must be less than 14 characters")
.min(6, "Display Name must be at least 6 characters long")
.matches(/^\S+$/, "Display Name cannot contain spaces"),
phone: Yup.string()
.required("Phone number is required"),
password: Yup.string()
......@@ -83,22 +78,22 @@ const validationSchema = Yup.object().shape({
confirmPassword: Yup.string()
.oneOf([Yup.ref('password')], 'Passwords must match')
.required('Confirm Password is required'),
dob: Yup.date()
.required("Date of birth is required")
.max(new Date(), 'Date of birth cannot be in the future')
.test('age', 'You must be at least 21 years old', function (value) {
if (!value) return true;
const cutoff = dayjs().subtract(21, 'years');
return dayjs(value).isBefore(cutoff);
}),
// dob: Yup.date()
// .required("Date of birth is required")
// .max(new Date(), 'Date of birth cannot be in the future')
// .test('age', 'You must be at least 21 years old', function (value) {
// if (!value) return true;
// const cutoff = dayjs().subtract(21, 'years');
// return dayjs(value).isBefore(cutoff);
// }),
first_name: Yup.string().required('First name is required'),
middle_name: Yup.string(),
last_name: Yup.string().required('Last name is required'),
// photoid_number: Yup.string().required('Photo ID is required'),
city: Yup.string(),
pob: Yup.string().required('Place of birth is required'),
// city: Yup.string(),
// pob: Yup.string().required('Place of birth is required'),
agree: Yup.boolean().required().oneOf([true], 'You must agree to the terms and conditions'),
country_code: Yup.string().required("Country code is required"),
// country_code: Yup.string().required("Country code is required"),
})
export default function RegisterPage() {
......@@ -106,51 +101,51 @@ export default function RegisterPage() {
const router = useRouter();
const dispatch = useAppDispatch();
const [countryCodes, setCountryCodes] = useState<any[]>([]);
useEffect(() => {
const fetchCountries = async () => {
try {
const res = await fetch(
"https://restcountries.com/v3.1/all?fields=name,idd"
);
if (!res.ok) {
throw new Error("Failed to fetch countries");
}
const data = await res.json();
const formatted = data
.filter((country: any) => country?.idd?.root)
.map((country: any) => {
const root = country.idd.root;
const suffix = country.idd.suffixes?.[0] || "";
// 🔥 Special case: If root is "+1", do NOT append suffix
const dialCode =
root === "+1" ? root : `${root}${suffix}`;
return {
name: country.name.common,
label: `${country.name.common} (${dialCode})`,
dialCode,
};
})
.sort((a: any, b: any) =>
a.name.localeCompare(b.name)
);
setCountryCodes(formatted);
} catch (error: any) {
console.error(
error?.message || "Country fetch failed"
);
}
};
fetchCountries();
}, []);
// const [countryCodes, setCountryCodes] = useState<any[]>([]);
// useEffect(() => {
// const fetchCountries = async () => {
// try {
// const res = await fetch(
// "https://restcountries.com/v3.1/all?fields=name,idd"
// );
// if (!res.ok) {
// throw new Error("Failed to fetch countries");
// }
// const data = await res.json();
// const formatted = data
// .filter((country: any) => country?.idd?.root)
// .map((country: any) => {
// const root = country.idd.root;
// const suffix = country.idd.suffixes?.[0] || "";
// // 🔥 Special case: If root is "+1", do NOT append suffix
// const dialCode =
// root === "+1" ? root : `${root}${suffix}`;
// return {
// name: country.name.common,
// label: `${country.name.common} (${dialCode})`,
// dialCode,
// };
// })
// .sort((a: any, b: any) =>
// a.name.localeCompare(b.name)
// );
// setCountryCodes(formatted);
// } catch (error: any) {
// console.error(
// error?.message || "Country fetch failed"
// );
// }
// };
// fetchCountries();
// }, []);
const initialValues = {
first_name: '',
middle_name: '',
......@@ -161,24 +156,24 @@ export default function RegisterPage() {
confirmPassword: "",
phone: "",
// photoid_number: '',
dob: null as Dayjs | null,
city: '',
pob: '',
// dob: null as Dayjs | null,
// city: '',
// pob: '',
agree: true,
visitor_id: undefined,
country_code: '',
// country_code: '',
}
const { deviceId } = useSeon();
const { handleSubmit, handleBlur, handleChange, errors, dirty, values, touched, setFieldValue, setFieldTouched } = useFormik(
const { handleSubmit, handleBlur, handleChange, errors, dirty, values, touched, setFieldValue } = useFormik(
{
initialValues,
validationSchema,
onSubmit: async (values) => {
const formattedDob = values.dob ? dayjs(values.dob).format('YYYY-MM-DD') : '';
// const formattedDob = values.dob ? dayjs(values.dob).format('YYYY-MM-DD') : '';
const userFromPropeelVisitorId = localStorage.getItem("visitor_id");
const cleanedPhone = values.phone.replace(/^0+/, '');
// const cleanedPhone = values.phone.replace(/^0+/, '');
const fullPhoneNumber = `${values.country_code}${cleanedPhone}`;
// const fullPhoneNumber = `${values.country_code}${cleanedPhone}`;
try {
const response = await registerUser({
......@@ -189,11 +184,11 @@ export default function RegisterPage() {
first_name: values.first_name,
middle_name: values.middle_name,
last_name: values.last_name,
phone: fullPhoneNumber,
phone: values.phone,
// photoid_number: values.photoid_number,
dob: formattedDob,
city: values.city,
pob: values.pob,
// dob: formattedDob,
// city: values.city,
// pob: values.pob,
agree: values.agree,
device_id: deviceId,
visitor_id: userFromPropeelVisitorId || undefined,
......@@ -291,7 +286,7 @@ export default function RegisterPage() {
</div>
{/* EMAIL */}
<div className="col-span-2 lg:col-span-6">
<div className="col-span-3 lg:col-span-3">
<div className="input_field">
<InputLabel htmlFor="emailAddress">Email Address<span className="text-red-500">*</span></InputLabel>
<OutlinedInput
......@@ -311,7 +306,7 @@ export default function RegisterPage() {
</div>
{/* DISPLAY NAME */}
<div className="col-span-2 lg:col-span-3">
<div className="col-span-3 lg:col-span-3">
<div className="input_field">
<InputLabel htmlFor="displayName">Display Name<span className="text-red-500">*</span></InputLabel>
<OutlinedInput
......@@ -330,7 +325,6 @@ export default function RegisterPage() {
</div>
</div>
{/* Photo ID */}
{/* <div className="col-span-2 lg:col-span-3">
<div className="input__field">
......@@ -347,7 +341,7 @@ export default function RegisterPage() {
</div> */}
{/* Address */}
<div className="col-span-2 lg:col-span-3">
{/* <div className="col-span-2 lg:col-span-3">
<div className="input__field">
<InputLabel htmlFor="city">City</InputLabel>
<OutlinedInput
......@@ -362,10 +356,10 @@ export default function RegisterPage() {
/>
<span className="error">{touched.city && errors.city}</span>
</div>
</div>
</div> */}
{/* Country */}
<div className="col-span-2 lg:col-span-3">
{/* <div className="col-span-2 lg:col-span-3">
<div className="input__field">
<InputLabel htmlFor="pob" >Place of Birth<span className="text-red-500">*</span></InputLabel>
<OutlinedInput
......@@ -380,10 +374,10 @@ export default function RegisterPage() {
/>
<span className="error">{touched.pob && errors.pob}</span>
</div>
</div>
</div> */}
{/* Phone */}
<div className="col-span-2 lg:col-span-2">
{/* <div className="col-span-2 lg:col-span-2">
<InputLabel>
Country Code <span className="text-red-500">*</span>
</InputLabel>
......@@ -413,8 +407,8 @@ export default function RegisterPage() {
/>
)}
/>
</div>
<div className="col-span-2 lg:col-span-4">
</div> */}
<div className="col-span-3 lg:col-span-6">
<div className="input__field">
<InputLabel htmlFor="phone">Phone <span className="text-red-500">*</span></InputLabel>
<OutlinedInput
......@@ -433,7 +427,7 @@ export default function RegisterPage() {
</div>
{/* DOB */}
<div className="col-span-2 lg:col-span-6">
{/* <div className="col-span-2 lg:col-span-6">
<div className="input__field">
<InputLabel htmlFor="dob">Date of Birth <span className="text-red-500">*</span></InputLabel>
<LocalizationProvider dateAdapter={AdapterDayjs}>
......@@ -493,9 +487,9 @@ export default function RegisterPage() {
/>
</LocalizationProvider>
</div>
</div>
</div> */}
<div className="col-span-2 lg:col-span-3">
<div className="col-span-3 lg:col-span-3">
<div className="input_field">
<PasswordField
name="password"
......@@ -509,7 +503,7 @@ export default function RegisterPage() {
</div>
</div>
<div className="col-span-2 lg:col-span-3">
<div className="col-span-3 lg:col-span-3">
<div className="input_field">
<PasswordField
name="confirmPassword"
......@@ -522,7 +516,7 @@ export default function RegisterPage() {
/>
</div>
</div>
<div className="col-span-4">
<div className="col-span-6">
<FormControlLabel
control={<Checkbox
checked={values.agree}
......
......@@ -135,7 +135,7 @@ export default function AddPlayerForm({ formik, id, data, loading, buttonLabel }
</div>
<div className="input__field">
<InputLabel htmlFor="address">Address</InputLabel>
<InputLabel htmlFor="address">Address <span className="text-red-500">*</span></InputLabel>
<OutlinedInput
fullWidth
id="address"
......@@ -151,7 +151,7 @@ export default function AddPlayerForm({ formik, id, data, loading, buttonLabel }
</div>
<div className="input__field">
<InputLabel htmlFor="city">City</InputLabel>
<InputLabel htmlFor="city">City <span className="text-red-500">*</span></InputLabel>
<OutlinedInput
fullWidth
id="city"
......@@ -167,7 +167,7 @@ export default function AddPlayerForm({ formik, id, data, loading, buttonLabel }
</div>
<div className="input__field">
<InputLabel htmlFor="phone">Phone</InputLabel>
<InputLabel htmlFor="phone">Phone <span className="text-red-500">*</span></InputLabel>
<OutlinedInput
fullWidth
id="phone"
......@@ -181,6 +181,21 @@ export default function AddPlayerForm({ formik, id, data, loading, buttonLabel }
{formik.touched.phone && formik.errors.phone ? formik.errors.phone : ""}
</span>
</div>
<div className="input__field">
<InputLabel htmlFor="zip_code">Zip Code <span className="text-red-500">*</span></InputLabel>
<OutlinedInput
fullWidth
id="zip_code"
name="zip_code"
placeholder="Enter zip code"
value={formik.values.zip_code}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
<span className="error">
{formik.touched.phone && formik.errors.phone ? formik.errors.phone : ""}
</span>
</div>
{/* DOB */}
<div className="input__field">
......
......@@ -18,11 +18,12 @@ export const PlayerValidationSchema = (isEdit: boolean) => Yup.object().shape({
first_name: Yup.string().required("First name is required"),
last_name: Yup.string().required("Last name is required"),
wallet_address: Yup.string().nullable(),
address: Yup.string().nullable(),
city: Yup.string().nullable(),
address: Yup.string().required("Address is required"),
city: Yup.string().required("City is required"),
zip_code: Yup.string().required("Zip code is required"),
phone: Yup.string()
.matches(/^\+?\d{7,15}$/, "Invalid phone number")
.nullable(),
.required("Phone is required"),
password: isEdit
? Yup.string().nullable() // not required in edit mode
: Yup.string().min(6, "Password must be at least 6 characters").required("Password is required"),
......@@ -67,6 +68,7 @@ export default function AddPlayerPage({ id }: { id?: string }) {
password_confirmation: data?.data.password_confirmation,
profile_image: null,
dob: data?.data.dob || null as Dayjs | null,
zip_code: data?.data.zip_code || "",
} : initialPlayerValues,
validationSchema: PlayerValidationSchema(!!id),
enableReinitialize: true,
......
......@@ -10,7 +10,6 @@ import dayjs, { Dayjs } from 'dayjs';
import { useFormik } from 'formik';
export default function EditUserProfile({ id, buttonLabel }: { id: string, buttonLabel?: string; }) {
const dispatch = useAppDispatch();
const [updateUserProfile, { isLoading }] = useUpdateUserProfileMutation();
const user = useAppSelector((state) => state?.auth.user);
......@@ -31,6 +30,7 @@ export default function EditUserProfile({ id, buttonLabel }: { id: string, butto
password_confirmation: '',
profile_image: null,
dob: user.dob || null as Dayjs | null,
zip_code: user.zip_code || "",
} : initialPlayerValues,
validationSchema: PlayerValidationSchema(user?.id ? true : false),
enableReinitialize: true,
......
// 'use client';
// import { useAppDispatch, useAppSelector } from '@/hooks/hook';
// import { useDepositMutation } from '@/services/transaction';
// import { showToast, ToastVariant } from '@/slice/toastSlice';
// import { DepositProps } from '@/types/transaction';
// import { Button, InputLabel, OutlinedInput } from '@mui/material';
// import { useRouter } from 'next/navigation';
// import Script from 'next/script';
// import { PaymentModeProps } from '.';
// declare global {
// interface Window {
// CollectJS: {
// configure: (config: {
// paymentType: string;
// callback: (response: any) => void;
// }) => void;
// };
// }
// }
// export default function PaymentForm({ id, amount, type }: DepositProps & { type: PaymentModeProps }) {
// const dispatch = useAppDispatch();
// const router = useRouter();
// const user = useAppSelector((state) => state.auth.user);
// const [payViaFortPay, { isLoading }] = useDepositMutation();
// const handleCollectJSLoad = () => {
// if (typeof window !== 'undefined' && window.CollectJS) {
// window.CollectJS.configure({
// paymentType: 'cc',
// callback: async (response) => {
// try {
// await payViaFortPay({
// id: id,
// amount: amount,
// type: type as PaymentModeProps,
// payment_token: response.token
// }).unwrap();
// router.push(`/buy-coins/${id}/success`)
// }
// catch (e: any) {
// dispatch(showToast({
// message: e?.data?.message || "Unable to deposit",
// variant: ToastVariant.ERROR
// }))
// }
// }
// });
// }
// };
// return (
// <>
// <Script
// src="https://secure.fppgateway.com/token/Collect.js"
// data-tokenization-key="NAhDuk-7V4u2u-tUAsT5-dCqbH5"
// strategy="afterInteractive"
// onReady={handleCollectJSLoad}
// />
// <form className="theForm">
// <div className="formInner flex flex-col gap-3 md:grid md:grid-cols-2">
// <div className="form-group">
// <InputLabel htmlFor="fname">First Name </InputLabel>
// <OutlinedInput type="text" className="form-control" placeholder="First Name" name="fname" defaultValue={user?.first_name} />
// </div>
// <div className="form-group">
// <InputLabel htmlFor="lname">Last Name </InputLabel>
// <OutlinedInput type="text" className="form-control" placeholder="Last Name" name="lname" defaultValue={user?.last_name} />
// </div>
// <div className="form-group">
// <InputLabel htmlFor="address1">Address </InputLabel>
// <OutlinedInput type="text" className="form-control" placeholder="Street Address" name="address1" defaultValue={user?.address} />
// </div>
// <div className="form-group">
// <InputLabel htmlFor="city">City </InputLabel>
// <OutlinedInput type="text" className="form-control" placeholder="City" name="city" defaultValue={user?.city} />
// </div>
// <div className="form-group">
// <InputLabel htmlFor="state">State </InputLabel>
// <OutlinedInput type="text" className="form-control" placeholder="State" name="state" defaultValue={user?.state} />
// </div>
// <div className="form-group">
// <InputLabel htmlFor="zip">Zip Code </InputLabel>
// <OutlinedInput type="text" className="form-control" placeholder="Zip code" name="zip" defaultValue={""} />
// </div>
// </div>
// {/* <OutlinedInput type="submit" id="payButton" value="Pay $5" className="btn btn-primary btn-block" /> */}
// <Button type="submit" id="payButton" variant='contained' color='primary' className='mt-4!'>{isLoading ? "Proceeding Payment" : "Proceed Payment"}</Button>
// </form>
// <div id="paymentTokenInfo"></div>
// </>
// );
// }
'use client';
import { useAppDispatch, useAppSelector } from '@/hooks/hook';
import { useDepositMutation } from '@/services/transaction';
import { showToast, ToastVariant } from '@/slice/toastSlice';
import { DepositProps } from '@/types/transaction';
import { Button, InputLabel, OutlinedInput } from '@mui/material';
import { Box, Button, FormHelperText, InputLabel, OutlinedInput, Typography } from '@mui/material';
import { useFormik } from 'formik';
import { useRouter } from 'next/navigation';
import Script from 'next/script';
import { useState } from 'react';
import * as Yup from 'yup';
import { PaymentModeProps } from '.';
declare global {
interface Window {
CollectJS: {
configure: (config: {
paymentType: string;
callback: (response: any) => void;
}) => void;
configure: (config: object) => void;
startPaymentRequest: () => void;
};
}
}
type CardFieldValidity = {
ccnumber: boolean;
ccexp: boolean;
cvv: boolean;
};
const billingSchema = Yup.object({
fname: Yup.string().required('First name is required'),
lname: Yup.string().required('Last name is required'),
address1: Yup.string().required('Address is required'),
city: Yup.string().required('City is required'),
state: Yup.string().required('State is required'),
zip: Yup.string()
.required('Zip code is required')
.matches(/^\d{5}(-\d{4})?$/, 'Enter a valid zip code'),
});
export default function PaymentForm({ id, amount, type }: DepositProps & { type: PaymentModeProps }) {
const dispatch = useAppDispatch();
const router = useRouter();
const user = useAppSelector((state) => state.auth.user);
const [payViaFortPay, { isLoading }] = useDepositMutation();
const [cardValidity, setCardValidity] = useState<CardFieldValidity>({
ccnumber: false,
ccexp: false,
cvv: false,
});
const [cardTouched, setCardTouched] = useState(false);
const formik = useFormik({
initialValues: {
fname: user?.first_name || '',
lname: user?.last_name || '',
address1: user?.address || '',
city: user?.city || '',
state: user?.state || '',
zip: '',
},
validationSchema: billingSchema,
onSubmit: () => {
setCardTouched(true);
const allCardValid = cardValidity.ccnumber && cardValidity.ccexp && cardValidity.cvv;
if (!allCardValid) return;
if (typeof window !== 'undefined' && window.CollectJS) {
window.CollectJS.startPaymentRequest();
}
},
});
const handleCollectJSLoad = () => {
if (typeof window !== 'undefined' && window.CollectJS) {
window.CollectJS.configure({
paymentType: 'cc',
callback: async (response) => {
try {
await payViaFortPay({
id: id,
amount: amount,
type: type as PaymentModeProps,
payment_token: response.token
}).unwrap();
router.push(`/buy-coins/${id}/success`)
}
catch (e: any) {
dispatch(showToast({
message: e?.data?.message || "Unable to deposit",
variant: ToastVariant.ERROR
}))
}
if (typeof window === 'undefined' || !window.CollectJS) return;
window.CollectJS.configure({
variant: 'inline',
customCss: {
'border-radius': '27px',
border: '1px solid rgba(255,255,255,0.2)',
padding: '12px 16px',
'font-family': 'Inter, sans-serif',
'font-size': '14px',
width: '100%',
'box-sizing': 'border-box',
color: '#000000',
'-webkit-text-fill-color': '#000000',
opacity: '1',
},
placeholderCss: {
color: 'rgba(0,0,0,0.4)',
},
callback: async (response: any) => {
try {
await payViaFortPay({
id,
amount,
type: type as PaymentModeProps,
payment_token: response.token,
}).unwrap();
router.push(`/buy-coins/${id}/success`);
} catch (e: any) {
dispatch(
showToast({
message: e?.data?.message || 'Unable to deposit',
variant: ToastVariant.ERROR,
})
);
}
});
}
},
// CollectJS calls this whenever a field's validity changes
validationCallback: (field: string, status: boolean, _message: string) => {
setCardValidity((prev) => ({ ...prev, [field]: status }));
},
fields: {
ccnumber: { selector: '#ccnumber', placeholder: 'Card Number' },
ccexp: { selector: '#ccexp', placeholder: 'MM / YY' },
cvv: { selector: '#cvv', placeholder: 'CVV' },
},
});
};
return (
<>
<Script
src="https://secure.fppgateway.com/token/Collect.js"
data-tokenization-key="NAhDuk-7V4u2u-tUAsT5-dCqbH5"
data-tokenization-key="5mN8N7-jhr55W-N22pxX-uAW2s9"
strategy="afterInteractive"
onReady={handleCollectJSLoad}
/>
<form className="theForm">
<form className="theForm" onSubmit={formik.handleSubmit} noValidate>
<div className="formInner flex flex-col gap-3 md:grid md:grid-cols-2">
{/* ── Billing fields ── */}
<div className="form-group">
<InputLabel htmlFor="fname">First Name </InputLabel>
<OutlinedInput type="text" className="form-control" placeholder="First Name" name="fname" defaultValue={user?.first_name} />
<InputLabel htmlFor="name">First Name<span className="text-red-500">*</span></InputLabel>
<OutlinedInput
id="fname"
name="fname"
type="text"
placeholder="First Name"
value={formik.values.fname}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
error={formik.touched.fname && Boolean(formik.errors.fname)}
fullWidth
/>
{formik.touched.fname && formik.errors.fname && (
<FormHelperText error sx={{ ml: '14px' }}>{formik.errors.fname}</FormHelperText>
)}
</div>
<div className="form-group">
<InputLabel htmlFor="lname">Last Name </InputLabel>
<OutlinedInput type="text" className="form-control" placeholder="Last Name" name="lname" defaultValue={user?.last_name} />
<InputLabel htmlFor="name">Last Name<span className="text-red-500">*</span></InputLabel>
<OutlinedInput
id="lname"
name="lname"
type="text"
placeholder="Last Name"
value={formik.values.lname}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
error={formik.touched.lname && Boolean(formik.errors.lname)}
fullWidth
/>
{formik.touched.lname && formik.errors.lname && (
<FormHelperText error sx={{ ml: '14px' }}>{formik.errors.lname}</FormHelperText>
)}
</div>
<div className="form-group">
<InputLabel htmlFor="address1">Address </InputLabel>
<OutlinedInput type="text" className="form-control" placeholder="Street Address" name="address1" defaultValue={user?.address} />
<InputLabel htmlFor="address1">Address<span className="text-red-500">*</span></InputLabel>
<OutlinedInput
id="address1"
name="address1"
type="text"
placeholder="Street Address"
value={formik.values.address1}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
error={formik.touched.address1 && Boolean(formik.errors.address1)}
fullWidth
/>
{formik.touched.address1 && formik.errors.address1 && (
<FormHelperText error sx={{ ml: '14px' }}>{formik.errors.address1}</FormHelperText>
)}
</div>
<div className="form-group">
<InputLabel htmlFor="city">City </InputLabel>
<OutlinedInput type="text" className="form-control" placeholder="City" name="city" defaultValue={user?.city} />
<InputLabel htmlFor="city">City<span className="text-red-500">*</span></InputLabel>
<OutlinedInput
id="city"
name="city"
type="text"
placeholder="City"
value={formik.values.city}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
error={formik.touched.city && Boolean(formik.errors.city)}
fullWidth
/>
{formik.touched.city && formik.errors.city && (
<FormHelperText error sx={{ ml: '14px' }}>{formik.errors.city}</FormHelperText>
)}
</div>
<div className="form-group">
<InputLabel htmlFor="state">State </InputLabel>
<OutlinedInput type="text" className="form-control" placeholder="State" name="state" defaultValue={user?.state} />
<InputLabel htmlFor="state">State<span className="text-red-500">*</span></InputLabel>
<OutlinedInput
id="state"
name="state"
type="text"
placeholder="State"
value={formik.values.state}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
error={formik.touched.state && Boolean(formik.errors.state)}
fullWidth
/>
{formik.touched.state && formik.errors.state && (
<FormHelperText error sx={{ ml: '14px' }}>{formik.errors.state}</FormHelperText>
)}
</div>
<div className="form-group">
<InputLabel htmlFor="zip">Zip Code </InputLabel>
<OutlinedInput type="text" className="form-control" placeholder="Zip code" name="zip" defaultValue={""} />
<InputLabel htmlFor="zip">Zip Code<span className="text-red-500">*</span></InputLabel>
<OutlinedInput
id="zip"
name="zip"
type="text"
placeholder="Zip code"
value={formik.values.zip}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
error={formik.touched.zip && Boolean(formik.errors.zip)}
fullWidth
/>
{formik.touched.zip && formik.errors.zip && (
<FormHelperText error sx={{ ml: '14px' }}>{formik.errors.zip}</FormHelperText>
)}
</div>
<Typography variant='h6' fontWeight={500}>FortPay Required Fields <span className="text-red-500">*</span></Typography>
{/* ── Inline CollectJS card fields ── */}
<Box className="md:col-span-2 flex flex-col gap-3 mt-1">
<div className="form-group">
<InputLabel htmlFor="ccnumber">Card Number<span className="text-red-500">*</span></InputLabel>
<Box id="ccnumber" />
{cardTouched && !cardValidity.ccnumber && (
<FormHelperText error sx={{ ml: '14px' }}>Card number is required</FormHelperText>
)}
</div>
<Box className="flex gap-3">
<div className="form-group flex-1">
<InputLabel htmlFor="ccexp">Expiration<span className="text-red-500">*</span></InputLabel>
<Box id="ccexp" />
{cardTouched && !cardValidity.ccexp && (
<FormHelperText error sx={{ ml: '14px' }}>Expiration is required</FormHelperText>
)}
</div>
<div className="form-group flex-1">
<InputLabel htmlFor="cvv">CVV<span className="text-red-500">*</span></InputLabel>
<Box id="cvv" />
{cardTouched && !cardValidity.cvv && (
<FormHelperText error sx={{ ml: '14px' }}>CVV is required</FormHelperText>
)}
</div>
</Box>
</Box>
</div>
{/* <OutlinedInput type="submit" id="payButton" value="Pay $5" className="btn btn-primary btn-block" /> */}
<Button type="submit" id="payButton" variant='contained' color='primary' className='mt-4!'>{isLoading ? "Proceeding Payment" : "Proceed Payment"}</Button>
<Button
type="submit"
id="payButton"
variant="contained"
color="primary"
className="mt-4!"
disabled={isLoading}
>
{isLoading ? 'Processing Payment…' : 'Proceed Payment'}
</Button>
</form>
<div id="paymentTokenInfo"></div>
<div id="paymentTokenInfo" />
</>
);
}
\ No newline at end of file
......@@ -98,7 +98,9 @@ export default function CheckoutPage({ amount, slug, bonus }: {
</GlassWrapper>
</div>
</div>
{currentPaymentMode === "fortpay" ? <PaymentForm id={slug} amount={amount} type={currentPaymentMode as PaymentModeProps} /> : ""}
{currentPaymentMode === "fortpay" ? <>
<PaymentForm id={slug} amount={amount} type={currentPaymentMode as PaymentModeProps} />
</> : ""}
{currentPaymentMode === "crypto" ? <Button type='submit' variant='contained' color='primary' className='!mt-3' onClick={async () => {
try {
if (currentPaymentMode === "crypto") {
......
......@@ -63,7 +63,7 @@ export const authApi = createApi({
},
})
}),
getAgeGateUuid: builder.query<GlobalResponse & { data: { age_verify_uuid: string, is_age_verified: boolean } }, void>({
getAgeGateUuid: builder.mutation<GlobalResponse & { data: { age_verify_uuid: string, is_age_verified: boolean } }, void>({
query: () => ({
url: `/api/user/age-verify`,
method: "GET",
......@@ -79,4 +79,4 @@ export const authApi = createApi({
})
})
export const { useLoginMutation, useRegisterUserMutation, useSendVerificationLinkAgainMutation, useForgotPasswordMutation, useVerifyOTPMutation, useResetPasswordMutation, useVerifyEmailMutation, useGetAgeGateUuidQuery, useVerifyAgeGateMutation } = authApi;
\ No newline at end of file
export const { useLoginMutation, useRegisterUserMutation, useSendVerificationLinkAgainMutation, useForgotPasswordMutation, useVerifyOTPMutation, useResetPasswordMutation, useVerifyEmailMutation, useGetAgeGateUuidMutation, useVerifyAgeGateMutation } = authApi;
\ No newline at end of file
......@@ -37,11 +37,11 @@ export interface RegisterProps extends LoginProps {
last_name: string;
phone: string;
// photoid_number: string;
dob: string;
city: string;
pob: string;
dob?: string;
city?: string;
pob?: string;
agree: boolean;
device_id?: string;
visitor_id?: string;
country_code: string;
country_code?: string;
}
\ No newline at end of file
......@@ -16,6 +16,8 @@ export interface CommonPlayerProps {
role?: string;
state?: string;
dob?: string | Dayjs | null;
zip_code?: string;
}
export interface PlayerProps extends CommonPlayerProps {
id?: string;
......@@ -40,6 +42,7 @@ export const initialPlayerValues: PlayerProps = {
password_confirmation: "",
profile_image: null,
dob: null as Dayjs | null,
zip_code: "",
};
type GameInformation = {
......
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