Commit 654ac5dc by Arjun Jhukal

updated the game listing at user dashboard

parent 90544e7e
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg> <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
\ No newline at end of file <g clipPath="url(#a)">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1"
fill="#666" />
</g>
<defs>
<clipPath id="a">
<path fill="#fff" d="M0 0h16v16H0z" />
</clipPath>
</defs>
</svg>
\ No newline at end of file
import React from 'react'
export default function BonusGames() {
return (
<div>BonusGames</div>
)
}
import React from 'react'
export default function Loading() {
return (
<div>loading</div>
)
}
// app/(dashboard)/exclusive-games/[id]/page.tsx
import ExlusiveGameDetail from "@/components/pages/dashboard/userDashboard/games/exclusiveGames/exclusiveGameDetail";
import { getSingleGame } from "@/serverApi/game";
export default async function UserGameDetail({ params }: { params: { id: string } }) {
// const game = await getSingleGame(params.id);
// return <ExlusiveGameDetail game={game.data} />;
return <h1>Game detail</h1>
}
// app/games/loading.tsx
export default function Loading() {
return (
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-4 animate-pulse">
{Array.from({ length: 6 }).map((_, i) => (
<div
key={i}
className="rounded-xl bg-gray-200 w-full h-[220px]"
/>
))}
</div>
);
}
import ExclusiveGamePage from '@/components/pages/dashboard/userDashboard/games/exclusiveGames'
import React from 'react'
export default function ExclusiveGames() {
return (
<ExclusiveGamePage/>
)
}
import PlayerDetailPage from '@/components/pages/dashboard/adminDashboard/players/playerDetail'
import React from 'react'
export default function PlayerDetail() {
return (
<PlayerDetailPage />
)
}
"use client";
import PageHeader from '@/components/molecules/PageHeader'
import AddPlayerForm from '@/components/pages/dashboard/adminDashboard/players/addPlayerForm';
import { useParams } from 'next/navigation';
import React from 'react'
export default function SinglePlayer() {
const params = useParams();
const id = params?.id as string;
return (
<>
<PageHeader />
<AddPlayerForm id={id} />
</>
)
}
import { Box, ClickAwayListener, Fade, IconButton, List, ListItem, Paper, Popper } from '@mui/material' import { Box, Button, ClickAwayListener, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Fade, IconButton, List, ListItem, Paper, Popper } from '@mui/material'
import { More } from '@wandersonalwes/iconsax-react' import { More } from '@wandersonalwes/iconsax-react'
import Link from 'next/link'; import Link from 'next/link';
import React, { useRef, useState } from 'react' import React, { useRef, useState } from 'react'
...@@ -8,12 +8,20 @@ export default function ActionGroup({ ...@@ -8,12 +8,20 @@ export default function ActionGroup({
}: { onView?: string; onEdit?: string; onDelete?: () => void }) { }: { onView?: string; onEdit?: string; onDelete?: () => void }) {
const anchorRef = useRef<HTMLButtonElement | null>(null) const anchorRef = useRef<HTMLButtonElement | null>(null)
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [confirmOpen, setConfirmOpen] = useState(false);
const handleToggle = () => setOpen((prev) => !prev); const handleToggle = () => setOpen((prev) => !prev);
const handleClose = (event: MouseEvent | TouchEvent) => { const handleClose = (event: MouseEvent | TouchEvent) => {
if (anchorRef.current?.contains(event.target as Node)) return; if (anchorRef.current?.contains(event.target as Node)) return;
setOpen(false); setOpen(false);
}; };
const handleConfirmDelete = async () => {
setConfirmOpen(false);
if (onDelete) await onDelete();
};
const id = open ? 'action' : undefined; const id = open ? 'action' : undefined;
return ( return (
<Box> <Box>
...@@ -48,13 +56,13 @@ export default function ActionGroup({ ...@@ -48,13 +56,13 @@ export default function ActionGroup({
<Link href={onView || ""} className='block py-3 px-4 hover:bg-[#FBF4FB]'>View Profile</Link> <Link href={onView || ""} className='block py-3 px-4 hover:bg-[#FBF4FB]'>View Profile</Link>
</ListItem> </ListItem>
<ListItem> <ListItem>
<Link href={onView || ""} className='block py-3 px-4 hover:bg-[#FBF4FB]'>Edit</Link> <Link href={onEdit || ""} className='block py-3 px-4 hover:bg-[#FBF4FB]'>Edit</Link>
</ListItem> </ListItem>
<ListItem> <ListItem>
<Link href={"#"} className='block py-3 px-4 hover:bg-[#FBF4FB]' <Link href={"#"} className='block py-3 px-4 hover:bg-[#FBF4FB]'
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
onDelete; setConfirmOpen(true);
}} }}
>Delete</Link> >Delete</Link>
</ListItem> </ListItem>
...@@ -64,6 +72,28 @@ export default function ActionGroup({ ...@@ -64,6 +72,28 @@ export default function ActionGroup({
</Fade> </Fade>
)} )}
</Popper> </Popper>
<Dialog
open={confirmOpen}
onClose={() => setConfirmOpen(false)}
>
<DialogTitle>Confirm Delete</DialogTitle>
<DialogContent>
<p className='text-para-light'>
Are you sure you want to delete this record? This action cannot be undone.
</p>
</DialogContent>
<DialogActions>
<Button color="error"
variant="contained" onClick={() => setConfirmOpen(false)}>Cancel</Button>
<Button
onClick={handleConfirmDelete}
color="success"
variant="contained"
>
Delete
</Button>
</DialogActions>
</Dialog>
</Box> </Box>
) )
} }
import GoldCoinIcon from '@/icons/GoldCoinIcon'
import { Box } from '@mui/material'
import React from 'react'
export default function CoinCard() {
return (
<Box sx={{
background: "linear-gradient(to right,#FFA325,#693C00)",
padding: "1px",
borderRadius: "40px"
}}>
<Box sx={{
background: "#2D2D30",
borderRadius: "40px"
}} className="flex justify-start items-center gap-1 py-2 pl-4 pr-8">
<GoldCoinIcon />
<div className="coins">
<strong className="text-[12px] leading-4 font-[600] text-[#FBA027] block">20,000</strong>
<span className="text-[9px] mt-[-2px] block">Gold Coins</span>
</div>
</Box>
</Box >
)
}
"use client"; "use client";
import React from "react"; import React from "react";
import { Snackbar, Alert, IconButton } from "@mui/material";
import { CloseCircle } from "@wandersonalwes/iconsax-react"; import { CloseCircle } from "@wandersonalwes/iconsax-react";
import { closeToast } from "@/slice/toastSlice"; import { closeToast } from "@/slice/toastSlice";
...@@ -12,49 +13,43 @@ export default function Toast() { ...@@ -12,49 +13,43 @@ export default function Toast() {
); );
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
React.useEffect(() => { React.useEffect(() => {
if (isActive && autoTimeout) { if (isActive && autoTimeout) {
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
dispatch(closeToast()); dispatch(closeToast());
}, duration || 3000); }, duration || 3000);
return () => clearTimeout(timeout); return () => clearTimeout(timeout);
} }
}, [isActive, duration, autoTimeout, dispatch]); }, [isActive, duration, autoTimeout, dispatch]);
if (!isActive) return null;
const variantStyles: Record<string, string> = {
success: "border-green-500 bg-green-50 text-green-800",
error: "border-red-500 bg-red-50 text-red-800",
warning: "border-yellow-500 bg-yellow-50 text-yellow-800",
info: "border-blue-500 bg-blue-50 text-blue-800",
};
const currentVariant = variant?.toLowerCase() || "info"; const currentVariant = variant?.toLowerCase() || "info";
return ( return (
<div <Snackbar
className={`z-[9999] fixed top-4 right-4 flex max-w-sm w-full items-start gap-3 rounded-xl border-l-4 px-4 py-3 shadow-lg transition-all duration-300 animate-in slide-in-from-right open={isActive}
data-[state=closed]:slide-out-to-right data-[state=closed]:fade-out anchorOrigin={{ vertical: "top", horizontal: "right" }}
${variantStyles[currentVariant]}`} onClose={() => dispatch(closeToast())}
autoHideDuration={autoTimeout ? duration || 3000 : null}
sx={{ zIndex: 9999 }}
> >
<div className="flex flex-1 flex-col"> <Alert
{variant && ( severity={currentVariant as "success" | "error" | "warning" | "info"}
<h4 className="text-sm font-semibold"> variant="filled"
{variant.charAt(0).toUpperCase() + variant.slice(1).toLowerCase()} sx={{ width: "100%" }}
</h4> action={
)} <IconButton
{message && <p className="text-sm leading-snug">{message}</p>} size="small"
</div> aria-label="close"
color="inherit"
<button
type="button"
onClick={() => dispatch(closeToast())} onClick={() => dispatch(closeToast())}
className="max-w-fit p-0 text-current transition-opacity hover:opacity-70"
> >
<CloseCircle size="32" color="#FF8A65" /> <CloseCircle size="16" />
</button> </IconButton>
</div> }
>
{message}
</Alert>
</Snackbar>
); );
} }
...@@ -10,6 +10,7 @@ export default function AdminSearchBar() { ...@@ -10,6 +10,7 @@ export default function AdminSearchBar() {
<div className="inpute__field relative"> <div className="inpute__field relative">
<OutlinedInput <OutlinedInput
type="search" type="search"
placeholder="Search game"
startAdornment={ startAdornment={
<InputAdornment position="start"> <InputAdornment position="start">
<IconButton edge="start"> <IconButton edge="start">
......
import Avatar from '@/components/atom/Avatar'; import Avatar from '@/components/atom/Avatar';
import { Transitions } from '@/components/molecules/Transition'; import { Transitions } from '@/components/molecules/Transition';
import { useAppDispatch } from '@/hooks/hook'; import { useAppDispatch, useAppSelector } from '@/hooks/hook';
import { PATH } from '@/routes/PATH'; import { PATH } from '@/routes/PATH';
import { clearTokens } from '@/slice/authSlice'; import { clearTokens } from '@/slice/authSlice';
import { Box, Button, ButtonBase, ClickAwayListener, Fade, List, ListItem, ListItemText, Paper, Popper, Stack, Typography } from '@mui/material' import { Box, Button, ButtonBase, ClickAwayListener, Fade, List, ListItem, ListItemText, Paper, Popper, Stack, Typography } from '@mui/material'
...@@ -8,6 +8,7 @@ import { ArrowDown2 } from '@wandersonalwes/iconsax-react'; ...@@ -8,6 +8,7 @@ import { ArrowDown2 } from '@wandersonalwes/iconsax-react';
import Link from 'next/link'; import Link from 'next/link';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import React, { useRef, useState } from 'react' import React, { useRef, useState } from 'react'
import UserProfileMenu from './UserProfileMenu';
const avataur1 = '/assets/images/avatar-6.png'; const avataur1 = '/assets/images/avatar-6.png';
export default function Profile() { export default function Profile() {
...@@ -18,6 +19,7 @@ export default function Profile() { ...@@ -18,6 +19,7 @@ export default function Profile() {
}; };
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const router = useRouter(); const router = useRouter();
const user = useAppSelector((state) => state.auth.user);
const handleClose = (event: MouseEvent | TouchEvent) => { const handleClose = (event: MouseEvent | TouchEvent) => {
if (anchorRef.current && anchorRef.current.contains(event.target)) { if (anchorRef.current && anchorRef.current.contains(event.target)) {
return; return;
...@@ -33,16 +35,34 @@ export default function Profile() { ...@@ -33,16 +35,34 @@ export default function Profile() {
aria-controls={open ? 'profile-grow' : undefined} aria-controls={open ? 'profile-grow' : undefined}
aria-haspopup="true" aria-haspopup="true"
onClick={handleToggle} onClick={handleToggle}
className='!hover:bg-transparent'
sx={{
padding: 0,
'&:hover': {
backgroundColor: 'transparent', // disables hover bg
},
'&:active': {
backgroundColor: 'transparent', // disables click bg
boxShadow: 'none', // disables ripple/box-shadow
},
'&:focus': {
backgroundColor: 'transparent', // disables focus bg
boxShadow: 'none', // disables focus shadow
},
}}
> >
<div className='hidden lg:flex items-center gap-1'> <div className='hidden lg:flex items-center gap-1'>
<Avatar alt="profile user" src={avataur1} /> <Avatar alt="profile user" src={avataur1} />
{user?.role.toLowerCase() !== "user" ? <>
<div> <div>
<strong className='text-[14px] leading-[120%] font-bold text-text-title block mb-1 text-nowrap'>{"Arjun Jhukal"}</strong> <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'> <p className='text-[12px] text-left leading-[120%] font-[500] text-para-light text-nowrap'>
UI/UX Designer {user?.role || "User"}
</p> </p>
</div> </div>
<ArrowDown2 size={14} /> <ArrowDown2 size={14} />
</> : ""}
</div> </div>
</Button> </Button>
<Popper <Popper
...@@ -65,6 +85,8 @@ export default function Profile() { ...@@ -65,6 +85,8 @@ export default function Profile() {
}} }}
> >
<ClickAwayListener onClickAway={handleClose}> <ClickAwayListener onClickAway={handleClose}>
{
user?.role.toLowerCase() !== "user" ? (
<List> <List>
<ListItem> <ListItem>
<ListItemText> <ListItemText>
...@@ -80,6 +102,11 @@ export default function Profile() { ...@@ -80,6 +102,11 @@ export default function Profile() {
</ListItem> </ListItem>
</List> </List>
) : (
<UserProfileMenu />
)
}
</ClickAwayListener> </ClickAwayListener>
</Paper> </Paper>
</Fade> </Fade>
......
import { Box } from '@mui/material'
import React from 'react' import React from 'react'
import Profile from '../Profile'
import AdminSearchBar from '../AdminHeader/AdminSearchBar'
import CoinCard from '@/components/molecules/CoinCard'
import GoldCoinIcon from '@/icons/GoldCoinIcon'
import SilverCoinIcon from '@/icons/SilverCoinIcon'
export default function UserHeader() { export default function UserHeader() {
return ( return (
<div>UserHeader</div> <Box className='flex items-center gap-4 justify-between w-full'>
<AdminSearchBar />
<div className="right flex items-center gap-4">
<Box sx={{
background: "linear-gradient(to right,#FFA325,#693C00)",
padding: "1px",
borderRadius: "40px"
}}>
<Box sx={{
background: "#2D2D30",
borderRadius: "40px"
}} className="flex justify-start items-center gap-1 py-2 pl-4 pr-8">
<GoldCoinIcon />
<div className="coins">
<strong className="text-[12px] leading-4 font-[600] text-[#FBA027] block">20,000</strong>
<span className="text-[9px] mt-[-2px] block">Gold Coins</span>
</div>
</Box>
</Box>
<Box sx={{
background: "linear-gradient(to right,#69A29D,#93E0D9)",
padding: "1px",
borderRadius: "40px"
}}>
<Box sx={{
background: "#2D2D30",
borderRadius: "40px"
}} className="flex justify-start items-center gap-1 py-2 pl-4 pr-8">
<SilverCoinIcon />
<div className="coins">
<strong className="text-[12px] leading-4 font-[600] text-[#93E0D8] block">20,000</strong>
<span className="text-[9px] mt-[-2px] block">Gold Coins</span>
</div>
</Box>
</Box >
<Profile />
</div>
</Box>
) )
} }
import { List, ListItem, ListItemButton, ListItemIcon, ListItemText } from "@mui/material";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useAppDispatch } from "@/hooks/hook";
import { clearTokens } from "@/slice/authSlice";
import { ArrowDown2, ArrowUp2, Coin, Logout, MoneySend, Profile, Wallet2 } from "@wandersonalwes/iconsax-react";
const UserProfileMenu = () => {
const router = useRouter();
const dispatch = useAppDispatch();
const menuItems = [
{
label: "Profile",
href: "/profile",
icon: <Profile size="20" />,
},
{
label: "Game Credentials",
href: "#",
icon: <Wallet2 size="20" />,
onClick: (e: React.MouseEvent) => {
e.preventDefault();
dispatch(clearTokens());
router.replace("/login");
},
textColor: "text-red-500",
},
{
label: "Deposit History",
href: "/deposit-history",
icon: <Coin size="20" />,
},
{
label: "Withdraw History",
href: "/withdraw-history",
icon: <MoneySend size="20" />,
},
{
label: "Logout",
href: "#",
icon: <Logout size="20" />,
onClick: (e: React.MouseEvent) => {
e.preventDefault();
dispatch(clearTokens());
router.replace("/login");
},
},
];
return (
<List>
{menuItems.map((item, idx) => (
<ListItem key={idx} disablePadding>
<ListItemButton
component={item.href ? Link : "button"}
href={item.href || undefined}
onClick={item.onClick}
className={`flex items-center py-3 px-4 hover:bg-[#FBF4FB] ${item.textColor || ""}`}
>
<ListItemIcon className="min-w-[30px] mr-1">{item.icon}</ListItemIcon>
<ListItemText primary={item.label} />
</ListItemButton>
</ListItem>
))}
</List>
);
};
export default UserProfileMenu;
...@@ -9,6 +9,7 @@ import { HambergerMenu } from '@wandersonalwes/iconsax-react'; ...@@ -9,6 +9,7 @@ import { HambergerMenu } from '@wandersonalwes/iconsax-react';
import AdminHeader from './AdminHeader'; import AdminHeader from './AdminHeader';
import UserHeader from './UserHeader'; import UserHeader from './UserHeader';
import { OutlinedInput } from '@mui/material'; import { OutlinedInput } from '@mui/material';
import { useAppSelector } from '@/hooks/hook';
interface AppBarProps extends MuiAppBarProps { interface AppBarProps extends MuiAppBarProps {
...@@ -22,7 +23,7 @@ const AppBar = styled(MuiAppBar, { ...@@ -22,7 +23,7 @@ const AppBar = styled(MuiAppBar, {
easing: theme.transitions.easing.sharp, easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen, duration: theme.transitions.duration.leavingScreen,
}), }),
backgroundColor: theme.palette.common.white, backgroundColor: theme.palette.background.paper,
boxShadow: "none", boxShadow: "none",
variants: [ variants: [
{ {
...@@ -44,7 +45,7 @@ export default function Header({ open, handleDrawerOpen }: { ...@@ -44,7 +45,7 @@ export default function Header({ open, handleDrawerOpen }: {
handleDrawerOpen: () => void; handleDrawerOpen: () => void;
}) { }) {
const user = { role: "ADMIN" } const user = useAppSelector((state) => state.auth.user);
return ( return (
<AppBar position="fixed" open={open}> <AppBar position="fixed" open={open}>
<Toolbar sx={{ gap: "16px" }}> <Toolbar sx={{ gap: "16px" }}>
......
...@@ -21,7 +21,7 @@ export default function UserMenu({ open }: { open: boolean }) { ...@@ -21,7 +21,7 @@ export default function UserMenu({ open }: { open: boolean }) {
<ListItemButton <ListItemButton
className={[ className={[
open ? "expanded" : "collapsed", open ? "expanded" : "collapsed",
pathname.startsWith(PATH.DASHBOARD.ROOT) ? "active" : "" pathname === PATH.DASHBOARD.ROOT ? "active" : ""
].join(" ")} ].join(" ")}
onClick={() => { router.push(PATH.DASHBOARD.ROOT) }} onClick={() => { router.push(PATH.DASHBOARD.ROOT) }}
...@@ -41,12 +41,10 @@ export default function UserMenu({ open }: { open: boolean }) { ...@@ -41,12 +41,10 @@ export default function UserMenu({ open }: { open: boolean }) {
className={[ className={[
open ? "expanded" : "collapsed", open ? "expanded" : "collapsed",
[ [
PATH.ADMIN.GAMES.ROOT, PATH.USER.GAMES.ROOT,
PATH.ADMIN.GAMES.ADD_GAME.ROOT,
"/edit-game"
].some(path => pathname.startsWith(path)) ? "active" : "" ].some(path => pathname.startsWith(path)) ? "active" : ""
].join(" ")} ].join(" ")}
onClick={() => { router.push(PATH.ADMIN.GAMES.ROOT) }} onClick={() => { router.push(PATH.USER.GAMES.ROOT) }}
> >
<ListItemIcon className={open ? "expanded" : "collapsed"}> <ListItemIcon className={open ? "expanded" : "collapsed"}>
...@@ -86,7 +84,11 @@ export default function UserMenu({ open }: { open: boolean }) { ...@@ -86,7 +84,11 @@ export default function UserMenu({ open }: { open: boolean }) {
<ListItemButton <ListItemButton
className={[ className={[
open ? "expanded" : "collapsed", open ? "expanded" : "collapsed",
pathname.startsWith(PATH.DASHBOARD.ROOT) ? "active" : "" [
PATH.ADMIN.GAMES.ROOT,
PATH.ADMIN.GAMES.ADD_GAME.ROOT,
"/edit-game"
].some(path => pathname.startsWith(path)) ? "active" : ""
].join(" ")} ].join(" ")}
onClick={() => { router.push(PATH.DASHBOARD.ROOT) }} onClick={() => { router.push(PATH.DASHBOARD.ROOT) }}
......
...@@ -50,8 +50,9 @@ const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== 'open' ...@@ -50,8 +50,9 @@ const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== 'open'
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
boxSizing: 'border-box', boxSizing: 'border-box',
'& .MuiDrawer-paper': { '& .MuiDrawer-paper': {
backgroundColor: theme.palette.common.white, backgroundColor: theme.palette.background.paper,
color: theme.palette.text.primary, color: theme.palette.text.primary,
padding: 0,
}, },
variants: [ variants: [
{ {
......
...@@ -5,9 +5,15 @@ import { flexRender, Table } from "@tanstack/react-table"; ...@@ -5,9 +5,15 @@ import { flexRender, Table } from "@tanstack/react-table";
interface CustomTableProps<TData> { interface CustomTableProps<TData> {
table: Table<TData>; table: Table<TData>;
loading?: boolean;
emptyMessage?: string;
skeletonRows?: number;
} }
export default function CustomTable<TData>({ table }: CustomTableProps<TData>) { export default function CustomTable<TData>({ table, loading = false,
emptyMessage = "No records found", skeletonRows = 5, }: CustomTableProps<TData>) {
const rowCount = table.getRowModel().rows.length;
const columnCount = table.getAllLeafColumns().length;
return ( return (
<table className="min-w-full border-collapse border border-gray-200 text-left"> <table className="min-w-full border-collapse border border-gray-200 text-left">
<thead> <thead>
...@@ -16,7 +22,7 @@ export default function CustomTable<TData>({ table }: CustomTableProps<TData>) { ...@@ -16,7 +22,7 @@ export default function CustomTable<TData>({ table }: CustomTableProps<TData>) {
{headerGroup.headers.map((header) => ( {headerGroup.headers.map((header) => (
<th <th
key={header.id} key={header.id}
className="text-[12px] font-[600] text-title p-2 py-6 px-6 bg-light-gray" className="text-[12px] font-[600] text-title p-2 py-4 px-4 bg-light-gray"
> >
{flexRender( {flexRender(
header.column.columnDef.header, header.column.columnDef.header,
...@@ -28,16 +34,41 @@ export default function CustomTable<TData>({ table }: CustomTableProps<TData>) { ...@@ -28,16 +34,41 @@ export default function CustomTable<TData>({ table }: CustomTableProps<TData>) {
))} ))}
</thead> </thead>
<tbody> <tbody>
{table.getRowModel().rows.map((row) => ( {loading ? (
<tr key={row.id} className="odd:bg-white even:bg-gray-50"> Array.from({ length: skeletonRows }).map((_, rowIndex) => (
{row.getVisibleCells().map((cell) => ( <tr key={`skeleton-${rowIndex}`} className="animate-pulse">
<td key={cell.id} className="p-2 py-6 px-6"> {Array.from({ length: columnCount }).map((_, colIndex) => (
{flexRender(cell.column.columnDef.cell, cell.getContext())} <td key={`skeleton-cell-${rowIndex}-${colIndex}`} className="p-2 py-4 px-4">
<div className="h-4 w-full rounded bg-gray-200" />
</td> </td>
))} ))}
</tr> </tr>
))
) : rowCount === 0 ? (
<tr>
<td
colSpan={columnCount}
className="text-center px-4 py-4 text-gray-500"
>
{emptyMessage}
</td>
</tr>
) : (
table.getRowModel().rows.map((row) => (
<tr key={row.id} className="">
{row.getVisibleCells().map((cell) => (
<td key={cell.id} className="px-4 py-4 ">
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</td>
))} ))}
</tr>
))
)}
</tbody> </tbody>
</table> </table>
); );
} }
...@@ -58,7 +58,7 @@ export default function LoginPage() { ...@@ -58,7 +58,7 @@ export default function LoginPage() {
); );
dispatch( dispatch(
setTokens({ setTokens({
accessToken: response.data.access_token, access_token: response.data.access_token,
// refreshToken: response.data?.refresh, // refreshToken: response.data?.refresh,
user: response.data?.user, user: response.data?.user,
}), }),
......
...@@ -3,7 +3,8 @@ ...@@ -3,7 +3,8 @@
import InputFile from '@/components/atom/InputFile' import InputFile from '@/components/atom/InputFile'
import PasswordField from '@/components/molecules/PasswordField' import PasswordField from '@/components/molecules/PasswordField'
import { useAppDispatch } from '@/hooks/hook' import { useAppDispatch } from '@/hooks/hook'
import { useCreatePlayerMutation } from '@/services/playerApi' import { PATH } from '@/routes/PATH'
import { useCreatePlayerMutation, useGetPlayerByIdQuery, useUpdatePlayerByIdMutation } from '@/services/playerApi'
import { showToast, ToastVariant } from '@/slice/toastSlice' import { showToast, ToastVariant } from '@/slice/toastSlice'
import { initialPlayerValues } from '@/types/player' import { initialPlayerValues } from '@/types/player'
import { Button, Input, InputLabel, OutlinedInput } from '@mui/material' import { Button, Input, InputLabel, OutlinedInput } from '@mui/material'
...@@ -12,7 +13,7 @@ import { useRouter } from 'next/navigation' ...@@ -12,7 +13,7 @@ import { useRouter } from 'next/navigation'
import React from 'react' import React from 'react'
import * as Yup from "yup"; import * as Yup from "yup";
export const PlayerValidationSchema = Yup.object().shape({ export const PlayerValidationSchema = (isEdit: boolean) => Yup.object().shape({
name: Yup.string().required("Username is required"), name: Yup.string().required("Username is required"),
email: Yup.string() email: Yup.string()
.email("Invalid email address") .email("Invalid email address")
...@@ -25,60 +26,58 @@ export const PlayerValidationSchema = Yup.object().shape({ ...@@ -25,60 +26,58 @@ export const PlayerValidationSchema = Yup.object().shape({
phone: Yup.string() phone: Yup.string()
.matches(/^\+?\d{7,15}$/, "Invalid phone number") .matches(/^\+?\d{7,15}$/, "Invalid phone number")
.nullable(), .nullable(),
password: Yup.string() password: isEdit
.min(6, "Password must be at least 6 characters") ? Yup.string().nullable() // not required in edit mode
.required("Password is required"), : Yup.string().min(6, "Password must be at least 6 characters").required("Password is required"),
password_confirmation: Yup.string() password_confirmation: Yup.string().when("password", {
.oneOf([Yup.ref("password")], "Passwords must match") is: (val: string) => !!val, // required only if password is filled
.required("Password confirmation is required"), then: (schema) => schema.oneOf([Yup.ref("password")], "Passwords must match").required("Password confirmation is required"),
otherwise: (schema) => schema.nullable(),
}),
// profile_image: Yup.mixed().required("Profile is required"), // profile_image: Yup.mixed().required("Profile is required"),
}); });
export default function AddPlayerForm({ id }: { id?: string | number }) { export default function AddPlayerForm({ id }: { id?: string }) {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const router = useRouter(); const router = useRouter();
const [createPlayer, { isLoading }] = useCreatePlayerMutation(); const [createPlayer, { isLoading }] = useCreatePlayerMutation();
const [updatePlayer, { isLoading: updating }] = useUpdatePlayerByIdMutation();
const { data, isLoading: loadingPlayer } = useGetPlayerByIdQuery(
id ? { id } : ({} as any),
{ skip: !id }
);
const formik = useFormik({ const formik = useFormik({
initialValues: initialPlayerValues, initialValues: data ? {
validationSchema: PlayerValidationSchema, name: data?.data.name,
// enableReinitialize, email: data?.data.email,
first_name: data?.data.first_name,
last_name: data?.data.last_name,
wallet_address: data?.data.wallet_address,
address: data?.data.address,
city: data?.data.city,
phone: data?.data.phone,
password: data?.data.password,
password_confirmation: data?.data.password_confirmation,
profile_image: null,
} : initialPlayerValues,
validationSchema: PlayerValidationSchema(!!id),
enableReinitialize: true,
onSubmit: async (values) => { onSubmit: async (values) => {
if (id) {
try {
console.log("Editing Player")
}
catch (e: any) {
dispatch(
showToast({
message: e.error,
variant: ToastVariant.ERROR
})
)
}
}
else {
try {
const formData = new FormData(); const formData = new FormData();
// Required fields
formData.append("name", values.name); formData.append("name", values.name);
formData.append("email", values.email); formData.append("email", values.email);
formData.append("first_name", values.first_name); formData.append("first_name", values.first_name);
formData.append("last_name", values.last_name); formData.append("last_name", values.last_name);
formData.append("password", values.password); formData.append("password", values.password);
formData.append("password_confirmation", values.password_confirmation); formData.append("password_confirmation", values.password_confirmation);
if (values.wallet_address) formData.append("wallet_address", values.wallet_address); if (values.wallet_address) formData.append("wallet_address", values.wallet_address);
if (values.address) formData.append("address", values.address); if (values.address) formData.append("address", values.address);
if (values.city) formData.append("city", values.city); if (values.city) formData.append("city", values.city);
if (values.phone) formData.append("phone", values.phone); if (values.phone) formData.append("phone", values.phone);
if (values.profile_image) { if (values.profile_image) {
if (Array.isArray(values.profile_image)) { if (Array.isArray(values.profile_image)) {
values.profile_image.forEach((file) => formData.append("profile_image", file)); values.profile_image.forEach((file) => formData.append("profile_image", file));
} else { } else {
...@@ -86,11 +85,16 @@ export default function AddPlayerForm({ id }: { id?: string | number }) { ...@@ -86,11 +85,16 @@ export default function AddPlayerForm({ id }: { id?: string | number }) {
} }
} }
const response = await createPlayer(formData).unwrap(); if (id && data) {
formData.append("profile_image_file", data?.data?.profile_image_file || "");
}
if (id) {
try {
const response = await updatePlayer({ id: id, body: formData });
dispatch( dispatch(
showToast({ showToast({
message: response.message, message: response?.data?.message || "User Updated Successfully",
variant: ToastVariant.SUCCESS variant: ToastVariant.SUCCESS
}) })
); );
...@@ -98,6 +102,26 @@ export default function AddPlayerForm({ id }: { id?: string | number }) { ...@@ -98,6 +102,26 @@ export default function AddPlayerForm({ id }: { id?: string | number }) {
router.push("/players"); router.push("/players");
} }
catch (e: any) { catch (e: any) {
dispatch(
showToast({
message: e.error || e.data.message,
variant: ToastVariant.ERROR
})
)
}
}
else {
try {
const response = await createPlayer(formData).unwrap();
dispatch(
showToast({
message: response.message,
variant: ToastVariant.SUCCESS
})
);
router.push("/players");
}
catch (e: any) {
console.log(e); console.log(e);
dispatch( dispatch(
showToast({ showToast({
...@@ -276,6 +300,7 @@ export default function AddPlayerForm({ id }: { id?: string | number }) { ...@@ -276,6 +300,7 @@ export default function AddPlayerForm({ id }: { id?: string | number }) {
value={formik.values.profile_image || null} value={formik.values.profile_image || null}
onChange={(file: File | File[] | null) => formik.setFieldValue("profile_image", file)} onChange={(file: File | File[] | null) => formik.setFieldValue("profile_image", file)}
onBlur={() => formik.setFieldTouched("profile_image", true)} onBlur={() => formik.setFieldTouched("profile_image", true)}
serverFile={data?.data?.profile_image_file}
/> />
<span className="error"> <span className="error">
{formik.touched.profile_image && formik.errors.profile_image ? formik.errors.profile_image : ""} {formik.touched.profile_image && formik.errors.profile_image ? formik.errors.profile_image : ""}
...@@ -284,8 +309,11 @@ export default function AddPlayerForm({ id }: { id?: string | number }) { ...@@ -284,8 +309,11 @@ export default function AddPlayerForm({ id }: { id?: string | number }) {
</div> </div>
</div> </div>
<div className="text-end mt-8 lg:mt-12 max-w-fit ml-auto"> <div className="text-end mt-8 lg:mt-12 max-w-fit ml-auto flex justify-end gap-4">
<Button type="submit" variant="contained" color="primary" sx={{ color: "#fff" }} disabled={!formik.dirty || formik.isSubmitting}> {id ? <Button color='error' variant='contained' onClick={() => {
router.push(PATH.ADMIN.PLAYERS.ROOT)
}}>Cancel Player Edit</Button> : null}
<Button type="submit" variant="contained" color="primary" sx={{ color: "#fff" }} >
Confirm {id ? "Player Update" : "Player Addition"} Confirm {id ? "Player Update" : "Player Addition"}
</Button> </Button>
</div> </div>
......
...@@ -10,7 +10,7 @@ import { showToast, ToastVariant } from '@/slice/toastSlice'; ...@@ -10,7 +10,7 @@ import { showToast, ToastVariant } from '@/slice/toastSlice';
import { PlayerItem, PlayerProps } from '@/types/player'; import { PlayerItem, PlayerProps } from '@/types/player';
import { formatDateTime } from '@/utils/formatDateTime'; import { formatDateTime } from '@/utils/formatDateTime';
import { getInitials } from '@/utils/getInitials'; import { getInitials } from '@/utils/getInitials';
import { Box } from '@mui/material'; import { Box, Checkbox, Pagination } from '@mui/material';
import { ColumnDef, getCoreRowModel, getPaginationRowModel, getSortedRowModel, useReactTable } from '@tanstack/react-table'; import { ColumnDef, getCoreRowModel, getPaginationRowModel, getSortedRowModel, useReactTable } from '@tanstack/react-table';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import React, { useMemo, useState } from 'react' import React, { useMemo, useState } from 'react'
...@@ -20,36 +20,33 @@ export default function PlayerListing() { ...@@ -20,36 +20,33 @@ export default function PlayerListing() {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const [sorting, setSorting] = useState<{ id: string; desc: boolean }[]>([]); const [sorting, setSorting] = useState<{ id: string; desc: boolean }[]>([]);
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(10);
const { data, isLoading: loadingPlayer } = useGetAllPlayerQuery(); const { data, isLoading: loadingPlayer } = useGetAllPlayerQuery({
const filteredData = useMemo(() => { page,
if (!data) return []; per_page: pageSize,
return data?.data?.data.filter((player: PlayerItem) => { search: search || ""
const name = player.name ?? "";
const email = player.email ?? "";
return (
name.toLowerCase().includes(search.toLowerCase()) ||
email.toLowerCase().includes(search.toLowerCase())
);
}); });
}, [search, data]);
const filteredData = useMemo(() => data?.data?.data || [], [data]);
const [deletePlayer, { isLoading: deletingPlayer }] = useDeletePlayerByIdMutation(); const [deletePlayer, { isLoading: deletingPlayer }] = useDeletePlayerByIdMutation();
const columns = useMemo<ColumnDef<PlayerItem>[]>(() => [ const columns = useMemo<ColumnDef<PlayerItem>[]>(() => [
{ {
id: 'select', id: 'select',
header: ({ table }) => ( header: ({ table }) => (
<input <Checkbox
type="checkbox" indeterminate={
table.getIsSomePageRowsSelected() &&
!table.getIsAllPageRowsSelected()
}
checked={table.getIsAllPageRowsSelected()} checked={table.getIsAllPageRowsSelected()}
onChange={table.getToggleAllPageRowsSelectedHandler()} onChange={table.getToggleAllPageRowsSelectedHandler()}
/> />
), ),
cell: ({ row }) => ( cell: ({ row }) => (
<input <Checkbox
type="checkbox"
checked={row.getIsSelected()} checked={row.getIsSelected()}
onChange={row.getToggleSelectedHandler()} onChange={row.getToggleSelectedHandler()}
/> />
...@@ -126,11 +123,12 @@ export default function PlayerListing() { ...@@ -126,11 +123,12 @@ export default function PlayerListing() {
onView={`${PATH.ADMIN.PLAYERS.ROOT}/${row.original.id}`} onView={`${PATH.ADMIN.PLAYERS.ROOT}/${row.original.id}`}
onEdit={`${PATH.ADMIN.PLAYERS.EDIT_PLAYER.ROOT}/${row.original.id}`} onEdit={`${PATH.ADMIN.PLAYERS.EDIT_PLAYER.ROOT}/${row.original.id}`}
onDelete={async () => { onDelete={async () => {
console.log("delete Clicked");
const response = await deletePlayer({ id: row.original.id }).unwrap(); const response = await deletePlayer({ id: row.original.id }).unwrap();
dispatch( dispatch(
showToast({ showToast({
message: response.message, message: response.message,
variant: ToastVariant.ERROR variant: ToastVariant.SUCCESS
}) })
) )
}} }}
...@@ -150,12 +148,37 @@ export default function PlayerListing() { ...@@ -150,12 +148,37 @@ export default function PlayerListing() {
return ( return (
<section className="player__listing_root"> <section className="player__listing_root">
<div className="border-gray border-solid border-[1px] rounded-[8px] lg:rounded-[16px]">
<TableHeader <TableHeader
search={search} search={search}
setSearch={setSearch} setSearch={setSearch}
onDownloadCSV={() => { }} onDownloadCSV={() => { }}
/> />
<CustomTable table={table} /> <CustomTable
table={table}
loading={loadingPlayer}
/>
<div className="flex justify-between items-center mt-4 px-8 py-6">
<div>
<span>Row per page:</span>
<select
value={pageSize}
onChange={(e) => setPageSize(Number(e.target.value))}
className="ml-2 border border-gray-300 rounded p-1"
>
{[5, 10, 15, 20].map((size) => (
<option key={size} value={size}>
{size}
</option>
))}
</select>
</div>
<Pagination count={data?.data?.pagination.total_pages || 1}
page={page}
onChange={(_, value) => setPage(value)} variant="outlined" shape="rounded" sx={{ gap: "8px" }} />
</div>
</div>
</section> </section>
) )
} }
import React from 'react'
export default function PlayerDetailPage() {
return (
<div>PlayerDetailPage</div>
)
}
import React from "react";
import { GameItem } from "@/types/game";
export default function ExlusiveGameDetail({ game }: { game: GameItem }) {
return (
<div className="p-4">
<h1 className="text-2xl font-bold">{game.name}</h1>
<p className="text-gray-600">{game.description}</p>
<p className="text-sm">Provider: {game.provider}</p>
</div>
);
}
import { PATH } from '@/routes/PATH';
import ProtectedLink from '@/routes/ProtectedLink';
import { getAllGames } from '@/serverApi/game';
import { Tooltip } from '@mui/material';
import Image from 'next/image';
import Link from 'next/link';
import React from 'react'
export default async function ExclusiveGamePage() {
const games = await getAllGames();
return (
<section className="exclusive__game__root">
<h2 className='mb-4 text-[20px] leading-[140%]'>Exclusive Games</h2>
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 xl:grid-cols-5 gap-4">
{
games?.data?.data.map((game) => (
<ProtectedLink href={`${PATH.USER.GAMES.ROOT}/${game.id}`} className="group block overflow-hidden hover:shadow-md transition rounded-2xl aspect-[208/222] relative" key={game.id}>
<Tooltip title={game.name}>
<Image
src={game.thumbnail || "/assets/images/fallback.png"}
alt={game.name}
fill
className="w-full h-[222px] object-cover group-hover:scale-105 transition-transform duration-300"
/>
</Tooltip>
</ProtectedLink>
))
}
</div>
</section>
)
}
...@@ -2,6 +2,7 @@ import { authApi } from "@/services/authApi"; ...@@ -2,6 +2,7 @@ import { authApi } from "@/services/authApi";
import { configureStore } from "@reduxjs/toolkit"; import { configureStore } from "@reduxjs/toolkit";
import auth from "@/slice/authSlice"; import auth from "@/slice/authSlice";
import toastSlice from "@/slice/toastSlice"; import toastSlice from "@/slice/toastSlice";
import authModalSlice from "@/slice/authModalSlice";
import { gameApi } from "@/services/gameApi"; import { gameApi } from "@/services/gameApi";
import { playerApi } from "@/services/playerApi"; import { playerApi } from "@/services/playerApi";
import { providerApi } from "@/services/providerApi"; import { providerApi } from "@/services/providerApi";
...@@ -10,6 +11,7 @@ export const store = configureStore({ ...@@ -10,6 +11,7 @@ export const store = configureStore({
reducer: { reducer: {
auth, auth,
toastSlice, toastSlice,
authModalSlice,
[authApi.reducerPath]: authApi.reducer, [authApi.reducerPath]: authApi.reducer,
[gameApi.reducerPath]: gameApi.reducer, [gameApi.reducerPath]: gameApi.reducer,
[providerApi.reducerPath]: providerApi.reducer, [providerApi.reducerPath]: providerApi.reducer,
......
...@@ -38,7 +38,7 @@ export const PATH = { ...@@ -38,7 +38,7 @@ export const PATH = {
}, },
USER: { USER: {
GAMES: { GAMES: {
ROOT: "/user/games", ROOT: "/exclusive-games",
} }
} }
} }
\ No newline at end of file
"use client";
import React from "react";
import { useRouter } from "next/navigation";
import { useAppDispatch, useAppSelector } from "@/hooks/hook";
import { openAuthModal } from "@/slice/authModalSlice";
interface Props {
href: string;
className?: string;
children: React.ReactNode;
}
export default function ProtectedLink({ href, className, children }: Props) {
const user = useAppSelector((s) => s.auth.user);
const dispatch = useAppDispatch();
const router = useRouter();
const handleClick = (e: React.MouseEvent) => {
e.preventDefault();
if (user) {
router.push(href);
} else {
dispatch(openAuthModal());
}
};
return (
<a href={href} onClick={handleClick} className={className}>
{children}
</a>
);
}
// lib/serverApi.ts
import { GameResponseProps, SingleGameResponse } from "@/types/game";
import { serverBaseQuery } from "./serverBaseQuery";
import { store } from "@/hooks/store";
export async function getAllGames(): Promise<GameResponseProps> {
// No token required
return serverBaseQuery<GameResponseProps>("/api/get-games");
}
console.log(store.getState());
export async function getSingleGame(id: string): Promise<SingleGameResponse> {
return serverBaseQuery<SingleGameResponse>(`/api/game/${id}`, {
token: store.getState().auth.access_token,
withAuth: true,
});
}
\ No newline at end of file
// lib/baseQuery.ts
import { RootState } from "@/hooks/store";
export async function serverBaseQuery<T>(
endpoint: string,
{
method = "GET",
token,
body,
cache = "no-store",
withAuth = false,
}: {
method?: string;
token?: string;
body?: unknown;
cache?: RequestCache;
withAuth?: boolean;
} = {}
): Promise<T> {
console.log("state", token);
const res = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}${endpoint}`, {
method,
cache,
headers: {
"Content-Type": "application/json",
...(token ? { Authorization: `Bearer ${token}` } : {}),
},
...(body ? { body: JSON.stringify(body) } : {}),
});
if (!res.ok) throw new Error(`Request failed: ${res.status} ${res.statusText}`);
return res.json();
}
import { createApi } from "@reduxjs/toolkit/query/react"; import { createApi } from "@reduxjs/toolkit/query/react";
import { baseQuery } from "./baseQuery"; import { baseQuery } from "./baseQuery";
import { PlayerListResponse, PlayerProps, SinlgePlayerResponseProps, } from "@/types/player"; import { PlayerListResponse, PlayerProps, SinlgePlayerResponseProps, } from "@/types/player";
import { GlobalResponse } from "@/types/config"; import { GlobalResponse, QueryParams } from "@/types/config";
export const playerApi = createApi({ export const playerApi = createApi({
reducerPath: "playerApi", reducerPath: "playerApi",
...@@ -16,11 +16,19 @@ export const playerApi = createApi({ ...@@ -16,11 +16,19 @@ export const playerApi = createApi({
}), }),
invalidatesTags: ["players"] invalidatesTags: ["players"]
}), }),
getAllPlayer: builder.query<PlayerListResponse, void>({ getAllPlayer: builder.query<PlayerListResponse, QueryParams>({
query: () => ({
url: "/api/admin/get-users", query: ({ search, page, per_page }) => {
const params = new URLSearchParams();
if (search) params.append('search', search);
if (page) params.append('page', page.toString());
if (per_page) params.append('page_size', per_page.toString());
const queryString = params.toString();
return {
url: `/api/admin/get-users${queryString ? `?${queryString}` : ''}`,
method: "GET" method: "GET"
}), }
},
providesTags: ['players'] providesTags: ['players']
}), }),
getPlayerById: builder.query<SinlgePlayerResponseProps, { id: number }>({ getPlayerById: builder.query<SinlgePlayerResponseProps, { id: number }>({
...@@ -30,24 +38,24 @@ export const playerApi = createApi({ ...@@ -30,24 +38,24 @@ export const playerApi = createApi({
}), }),
providesTags: ['players'] providesTags: ['players']
}), }),
getPlayerBalanceById: builder.query<SinlgePlayerResponseProps, { id: number }>({ getPlayerBalanceById: builder.query<SinlgePlayerResponseProps, { id: string }>({
query: ({ id }) => ({ query: ({ id }) => ({
url: `/api/admin/get-balance/${id}`, url: `/api/admin/get-balance/${id}`,
method: "GET" method: "GET"
}), }),
providesTags: ['players'] providesTags: ['players']
}), }),
updatePlayerById: builder.mutation<SinlgePlayerResponseProps, { id: number, data: FormData }>({ updatePlayerById: builder.mutation<SinlgePlayerResponseProps, { id: string, body: FormData }>({
query: ({ id, data }) => ({ query: ({ id, body }) => ({
url: `/api/admin/update-user/${id}`, url: `/api/admin/update-user/${id}`,
method: "POST", method: "POST",
body: data body: body
}), }),
invalidatesTags: ["players"] invalidatesTags: ["players"]
}), }),
deletePlayerById: builder.mutation<GlobalResponse, { id: string }>({ deletePlayerById: builder.mutation<GlobalResponse, { id: string }>({
query: ({ id }) => ({ query: ({ id }) => ({
url: `/api/admin/update-user/${id}`, url: `/api/admin/user/${id}`,
method: "DELETE", method: "DELETE",
}), }),
invalidatesTags: ["players"] invalidatesTags: ["players"]
......
// src/slice/uiSlice.ts
import { createSlice } from "@reduxjs/toolkit";
interface authModalSlice {
authModalOpen: boolean;
}
const initialState: authModalSlice = {
authModalOpen: false,
};
const authModalSlice = createSlice({
name: "authModalSlice",
initialState,
reducers: {
openAuthModal(state) {
state.authModalOpen = true;
},
closeAuthModal(state) {
state.authModalOpen = false;
},
},
});
export const { openAuthModal, closeAuthModal } = authModalSlice.actions;
export default authModalSlice.reducer;
...@@ -16,7 +16,7 @@ let localStorageUser = null; ...@@ -16,7 +16,7 @@ let localStorageUser = null;
if (tokens) { if (tokens) {
try { try {
const parsedTokens = JSON.parse(tokens); const parsedTokens = JSON.parse(tokens);
localStorageAccessToken = parsedTokens.accessToken || ""; localStorageAccessToken = parsedTokens.access_token || "";
localStorageRefreshToken = parsedTokens.refreshToken || ""; localStorageRefreshToken = parsedTokens.refreshToken || "";
localStorageUser = parsedTokens.user || ""; localStorageUser = parsedTokens.user || "";
} catch (error) { } catch (error) {
...@@ -36,20 +36,20 @@ export const authSlice = createSlice({ ...@@ -36,20 +36,20 @@ export const authSlice = createSlice({
initialState, initialState,
reducers: { reducers: {
setTokens: (state, action) => { setTokens: (state, action) => {
const { accessToken, refreshToken, user } = action.payload; const { access_token, refreshToken, user } = action.payload;
state.access_token = accessToken; state.access_token = access_token;
// state.refreshToken = refreshToken; // state.refreshToken = refreshToken;
state.user = user; state.user = user;
localStorage.setItem( localStorage.setItem(
"token", "token",
JSON.stringify({ accessToken, refreshToken, user }), JSON.stringify({ access_token, refreshToken, user }),
); );
if (isBrowser) { if (isBrowser) {
localStorage.setItem( localStorage.setItem(
"token", "token",
JSON.stringify({ accessToken, refreshToken, user }), JSON.stringify({ access_token, refreshToken, user }),
); );
} }
}, },
......
...@@ -99,14 +99,12 @@ export default function Palette(mode: ThemeMode) { ...@@ -99,14 +99,12 @@ export default function Palette(mode: ThemeMode) {
}, },
}, },
}, },
MuiOutlinedInput: { MuiOutlinedInput: {
styleOverrides: { styleOverrides: {
root: { root: {
fontSize: "14px", fontSize: "14px",
width: "100%", width: "100%",
borderRadius: "var(--border-radius-lg, 10px)", borderRadius: "10px",
// border: "1px solid var(--Gray, #E0E0E3)", // border: "1px solid var(--Gray, #E0E0E3)",
background: "transparent", background: "transparent",
color: "inherit", color: "inherit",
...@@ -130,6 +128,25 @@ export default function Palette(mode: ThemeMode) { ...@@ -130,6 +128,25 @@ export default function Palette(mode: ThemeMode) {
}, },
}, },
}, },
MuiCheckbox: {
styleOverrides: {
root: {
padding: 0,
width: 20,
height: 20,
"& .MuiSvgIcon-root": {
fontSize: 20,
borderRadius: 4,
border: "1px solid #71717A",
},
"&.Mui-checked .MuiSvgIcon-root": {
backgroundColor: "#7C3AED",
color: "#fff",
border: "1px solid #7C3AED",
},
},
},
},
MuiIconButton: { MuiIconButton: {
styleOverrides: { styleOverrides: {
root: { root: {
...@@ -269,7 +286,41 @@ export default function Palette(mode: ThemeMode) { ...@@ -269,7 +286,41 @@ export default function Palette(mode: ThemeMode) {
padding: "16px 8px" padding: "16px 8px"
} }
} }
} },
MuiPaginationItem: {
styleOverrides: {
root: {
fontSize: "12px",
color: "#71717A",
width: 28,
height: 28,
display: "flex",
alignItems: "center",
justifyContent: "center",
borderRadius: "4px",
border: "1px solid #E0E0E3",
fontWeight: 500,
"&:hover": {
border: "1px solid #B801C0",
background: " #F8B1FE",
},
"&.Mui-selected": {
border: "1px solid #B801C0",
background: " #F8B1FE",
color: "#B801C0",
},
},
},
},
MuiPagination: {
styleOverrides: {
ul: {
gap: "8px", // sets the spacing between pagination items
},
},
},
}, },
}); });
......
...@@ -22,16 +22,16 @@ export default function ThemeCustomization({ children }: { children: React.React ...@@ -22,16 +22,16 @@ export default function ThemeCustomization({ children }: { children: React.React
React.useEffect(() => { React.useEffect(() => {
if (!user || !user.role) { if (!user || !user.role) {
setTheme(ThemeMode.DARK); setTheme(ThemeMode.DARK);
setPalette(AdminPalette(ThemeMode.DARK));
} else if (user.role.toUpperCase() === "USER") {
setTheme(ThemeMode.DARK);
setPalette(Palette(ThemeMode.DARK)); setPalette(Palette(ThemeMode.DARK));
} else if (user.role.toUpperCase() === "ADMIN") {
setTheme(ThemeMode.LIGHT);
setPalette(AdminPalette(ThemeMode.LIGHT));
} else { } else {
setTheme(ThemeMode.LIGHT); setTheme(ThemeMode.LIGHT);
setPalette(Palette(ThemeMode.LIGHT)); setPalette(AdminPalette(ThemeMode.LIGHT));
} }
}, [user]); }, [user]);
// const customTheme = Palette(theme);
return ( return (
<StyledEngineProvider injectFirst> <StyledEngineProvider injectFirst>
......
...@@ -74,7 +74,6 @@ export default function Palette(mode: ThemeMode) { ...@@ -74,7 +74,6 @@ export default function Palette(mode: ThemeMode) {
root: { root: {
// color: 'rgba(255, 255, 255, 0.80)', // color: 'rgba(255, 255, 255, 0.80)',
color: titleColors[0], color: titleColors[0],
fontFamily: '"Hiragino Sans"',
fontSize: '12px', fontSize: '12px',
fontWeight: 400, fontWeight: 400,
lineHeight: 'normal', lineHeight: 'normal',
......
...@@ -32,3 +32,9 @@ export interface GlobalResponse { ...@@ -32,3 +32,9 @@ export interface GlobalResponse {
data: [], data: [],
message: string message: string
} }
export interface QueryParams {
page?: number;
per_page?: number;
search?: string;
}
\ No newline at end of file
...@@ -7,6 +7,7 @@ export interface FileResponse { ...@@ -7,6 +7,7 @@ export interface FileResponse {
export interface CommonGameProps { export interface CommonGameProps {
name: string; name: string;
category?: string; category?: string;
description: string; description: string;
api: string; api: string;
provider: string; provider: string;
......
import { ImageProps } from "./config"; import { ImageProps } from "./config";
import { Pagination } from "./game"; import { Pagination } from "./game";
export interface PlayerProps {
export interface CommonPlayerProps {
name: string; name: string;
email: string; email: string;
first_name: string; first_name: string;
...@@ -12,11 +13,12 @@ export interface PlayerProps { ...@@ -12,11 +13,12 @@ export interface PlayerProps {
phone?: string; phone?: string;
password: string; password: string;
password_confirmation: string; password_confirmation: string;
}
export interface PlayerProps extends CommonPlayerProps {
profile_image: File | null; profile_image: File | null;
profile_image_file?: string;
} }
export const initialPlayerValues: PlayerProps = { export const initialPlayerValues: PlayerProps = {
name: "", name: "",
email: "", email: "",
...@@ -31,12 +33,13 @@ export const initialPlayerValues: PlayerProps = { ...@@ -31,12 +33,13 @@ export const initialPlayerValues: PlayerProps = {
profile_image: null, profile_image: null,
}; };
export interface PlayerItem extends PlayerProps { export interface PlayerItem extends CommonPlayerProps {
id: string; id: string;
registered_date: string | Date; registered_date: string | Date;
current_credit?: string, current_credit?: string,
total_withdrawl?: string, total_withdrawl?: string,
total_deposited?: string total_deposited?: string
profile_image_file?: string;
} }
export interface PlayerListResponse { export interface PlayerListResponse {
......
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