Commit bd1b5dd7 by Arjun Jhukal

updated the chagne password functionality at logged in user profile

parent bca8509d
......@@ -2,6 +2,7 @@ import GlassWrapper from '@/components/molecules/GlassWrapper';
import TabController from '@/components/molecules/TabController';
import UserProfileCard from "@/components/organism/Cards/UserProfileCard";
import ResetPasswordForm from '@/components/pages/auth/resetPassword/ResetPasswordForm';
import EditUserProfile from '@/components/pages/dashboard/userDashboard/account/profile/editProfile';
import EditUserWallet from '@/components/pages/dashboard/userDashboard/account/profile/editUserWallet';
import { useAppSelector } from '@/hooks/hook';
......@@ -14,11 +15,14 @@ export default function AccountTab() {
const { data, isLoading } = useGetUserGameBalanceQuery();
const [currentActiveTab, setCurrentActiveTab] = React.useState<profileTabProps>("account_detail");
const handleTabChange = (tab: string) => {
setCurrentActiveTab(tab as profileTabProps);
};
const user = useAppSelector((state) => state.auth.user);
console.log(user);
const renderTabContent = () => {
switch (currentActiveTab) {
case "account_detail":
......@@ -27,7 +31,7 @@ export default function AccountTab() {
return <EditUserWallet />;
case "change_password":
return (<div className="px-8 lg:pt-8 pb-8">
<h2> Change Password</h2>
<ResetPasswordForm email={user?.email} />
</div>);
default:
return null;
......
import PasswordField from '@/components/molecules/PasswordField'
import { useAppDispatch } from '@/hooks/hook'
import { PATH } from '@/routes/PATH'
import { useResetPasswordMutation } from '@/services/authApi'
import { clearTokens } from '@/slice/authSlice'
import { showToast, ToastVariant } from '@/slice/toastSlice'
import { Box, InputLabel, OutlinedInput } from '@mui/material'
import { ArrowLeft } from '@wandersonalwes/iconsax-react'
import { useFormik } from 'formik'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import React from 'react'
import * as Yup from 'yup';
const validationSchema = Yup.object().shape({
emailAddress: Yup.string()
.email('Must be a valid email')
.max(255)
.required('Email is required'),
password: Yup.string()
.required('Password is required')
.test(
'no-leading-trailing-whitespace',
'Password cannot start or end with spaces',
(value) => value === value?.trim()
)
.max(16, 'Password must be less than 10 characters'),
confirmPassword: Yup.string()
.oneOf([Yup.ref('password')], 'Passwords must match')
.required('Confirm Password is required'),
})
export default function ResetPasswordForm({ email }: { email?: string }) {
const router = useRouter();
const dispatch = useAppDispatch();
const initialValues = {
emailAddress: email || "",
password: "",
confirmPassword: "",
}
const [resetPassword, { isLoading }] = useResetPasswordMutation();
const { handleSubmit, handleBlur, handleChange, errors, dirty, values, touched } = useFormik(
{
initialValues,
validationSchema,
onSubmit: async (values) => {
try {
const response = await resetPassword({
email: values.emailAddress,
password: values.password,
password_confirmation: values.confirmPassword,
}).unwrap();
dispatch(
showToast({
message: response?.message || "New password is updated",
variant: ToastVariant.SUCCESS,
autoTime: true,
}),
);
dispatch(clearTokens());
router.replace(PATH.AUTH.LOGIN.ROOT);
}
catch (e: any) {
console.log("Error", e);
dispatch(
showToast({
message: e?.data?.message || "Unable to reset password. Try again later",
variant: ToastVariant.ERROR,
autoTime: true,
}),
);
}
}
}
)
return (
<form action="" onSubmit={handleSubmit}>
<div className="grid md:grid-cols-2 gap-x-3 gap-y-5">
{!email ? <div className="col-span-2">
<div className="input_field">
<InputLabel htmlFor="emailAddress">Email Address*</InputLabel>
<OutlinedInput
name='emailAddress'
id='emailAddress'
placeholder='example@example.com'
value={values.emailAddress}
onChange={handleChange}
onBlur={handleBlur}
error={Boolean(touched.emailAddress && errors.emailAddress)}
/>
{
touched.emailAddress && errors.emailAddress ?
<span className="error ">{errors.emailAddress}</span> : null
}
</div>
</div> : ""}
<div className="col-span-2">
<div className="input_field">
<PasswordField
name="password"
label="Password*"
placeholder="XXXXXXX"
value={values.password}
onChange={handleChange}
onBlur={handleBlur}
error={touched.password ? errors.password : undefined}
/>
</div>
</div>
<div className="col-span-2">
<div className="input_field">
<PasswordField
name="confirmPassword"
label="Confirm Password*"
placeholder="XXXXXXX"
value={values.confirmPassword}
onChange={handleChange}
onBlur={handleBlur}
error={touched.confirmPassword ? errors.confirmPassword : undefined}
/>
</div>
</div>
</div>
<div className="action__group text-center flex flex-col gap-4 mt-8">
<button className='ss-btn bg-primary-grad' disabled={!dirty}>{isLoading ? "Changing Password" : "Reset Password"}</button>
<Link href={PATH.DASHBOARD.ROOT} className='ss-btn bg-secondary-grad'>Login</Link>
</div>
</form>
)
}
"use client";
import React, { useEffect, useState } from 'react'
import React from 'react'
import AuthMessageBlock from '../authMessageBlock'
import { Box, InputLabel, OutlinedInput } from '@mui/material'
import PasswordField from '@/components/molecules/PasswordField'
import { ArrowLeft } from '@wandersonalwes/iconsax-react'
import Link from 'next/link'
import { PATH } from '@/routes/PATH'
import * as Yup from 'yup';
import { useFormik } from 'formik';
import { useAppDispatch } from '@/hooks/hook'
import { showToast, ToastVariant } from '@/slice/toastSlice';
import { useResetPasswordMutation } from '@/services/authApi';
import { useRouter, useSearchParams } from 'next/navigation';
import ResetPasswordForm from './ResetPasswordForm';
import { Box } from '@mui/material';
import Link from 'next/link';
import { ArrowLeft } from '@wandersonalwes/iconsax-react';
import { PATH } from '@/routes/PATH';
const validationSchema = Yup.object().shape({
emailAddress: Yup.string()
.email('Must be a valid email')
.max(255)
.required('Email is required'),
password: Yup.string()
.required('Password is required')
.test(
'no-leading-trailing-whitespace',
'Password cannot start or end with spaces',
(value) => value === value?.trim()
)
.max(16, 'Password must be less than 10 characters'),
confirmPassword: Yup.string()
.oneOf([Yup.ref('password')], 'Passwords must match')
.required('Confirm Password is required'),
})
export default function ResetPasswordPage() {
const router = useRouter();
const dispatch = useAppDispatch();
const initialValues = {
emailAddress: "",
password: "",
confirmPassword: "",
}
const [resetPassword, { isLoading }] = useResetPasswordMutation();
const { handleSubmit, handleBlur, handleChange, errors, dirty, values, touched } = useFormik(
{
initialValues,
validationSchema,
onSubmit: async (values) => {
try {
const response = await resetPassword({
email: values.emailAddress,
password: values.password,
password_confirmation: values.confirmPassword,
}).unwrap();
dispatch(
showToast({
message: response?.message || "New password is updated",
variant: ToastVariant.SUCCESS,
autoTime: true,
}),
);
router.replace(PATH.AUTH.LOGIN.ROOT);
}
catch (e: any) {
console.log("Error", e);
dispatch(
showToast({
message: e?.data?.message || "Unable to reset password. Try again later",
variant: ToastVariant.ERROR,
autoTime: true,
}),
);
}
}
}
)
return (
<>
<AuthMessageBlock
......@@ -95,61 +27,7 @@ export default function ResetPasswordPage() {
<Link href={PATH.DASHBOARD.ROOT} className='text-[12px] leading-[120%] font-bold lg:text-[16px] hover:text-primary flex gap-2 items-center'><ArrowLeft />Back to Dashboard</Link>
<h1 className="text-[24px] leading-[120%] font-bold lg:text-[48px]">Forgot Password</h1>
</div>
<form action="" onSubmit={handleSubmit}>
<div className="grid md:grid-cols-2 gap-x-3 gap-y-5">
<div className="col-span-2">
<div className="input_field">
<InputLabel htmlFor="emailAddress">Email Address*</InputLabel>
<OutlinedInput
name='emailAddress'
id='emailAddress'
placeholder='example@example.com'
value={values.emailAddress}
onChange={handleChange}
onBlur={handleBlur}
error={Boolean(touched.emailAddress && errors.emailAddress)}
/>
{
touched.emailAddress && errors.emailAddress ?
<span className="error ">{errors.emailAddress}</span> : null
}
</div>
</div>
<div className="col-span-2">
<div className="input_field">
<PasswordField
name="password"
label="Password*"
placeholder="XXXXXXX"
value={values.password}
onChange={handleChange}
onBlur={handleBlur}
error={touched.password ? errors.password : undefined}
/>
</div>
</div>
<div className="col-span-2">
<div className="input_field">
<PasswordField
name="confirmPassword"
label="Confirm Password*"
placeholder="XXXXXXX"
value={values.confirmPassword}
onChange={handleChange}
onBlur={handleBlur}
error={touched.confirmPassword ? errors.confirmPassword : undefined}
/>
</div>
</div>
</div>
<div className="action__group text-center flex flex-col gap-4 mt-8">
<button className='ss-btn bg-primary-grad' disabled={!dirty}>{isLoading ? "Changing Password" : "Reset Password"}</button>
<Link href={PATH.DASHBOARD.ROOT} className='ss-btn bg-secondary-grad'>Login</Link>
</div>
</form>
<ResetPasswordForm />
</Box>
</>
)
......
......@@ -7,22 +7,14 @@ import { useReactTable, getCoreRowModel, getPaginationRowModel, getSortedRowMode
import { Pagination, Box } from '@mui/material';
import { TransactionStatusProps } from '../transaction/TransactionTable';
import { StatusOptions } from '@/types/config';
import { useGetAllActivityQuery } from '@/services/notificationApi';
import { ActivityProps } from '@/types/notification';
interface Activity {
id: number;
type: string;
user: string;
userId: string;
action: string;
details: string;
timestamp: string;
status: string;
icon: React.ReactNode;
}
export default function Activities() {
const activityTypes = [
{ value: 'all', label: 'All Activities' },
{ value: '', label: 'All Activities' },
{ value: 'registration', label: 'Registration' },
{ value: 'login', label: 'Login/Logout' },
{ value: 'deposit', label: 'Deposits' },
......@@ -33,140 +25,6 @@ export default function Activities() {
{ value: 'bonus', label: 'Bonuses' }
];
const activities: Activity[] = [
{
id: 1,
type: 'registration',
user: 'john_doe',
userId: 'USR001',
action: 'New user registered',
details: 'Account created successfully',
timestamp: '2025-10-16 14:30:25',
status: 'success',
icon: <User />
},
{
id: 2,
type: 'deposit',
user: 'sarah_smith',
userId: 'USR042',
action: 'Cash deposit',
details: 'Amount: $500.00 via Credit Card',
timestamp: '2025-10-16 14:25:10',
status: 'success',
icon: <User />
},
{
id: 3,
type: 'withdrawal',
user: 'mike_wilson',
userId: 'USR078',
action: 'Withdrawal request',
details: 'Amount: $250.00 to Bank Account',
timestamp: '2025-10-16 14:20:45',
status: 'pending',
icon: <User />
},
{
id: 4,
type: 'password_update',
user: 'emma_brown',
userId: 'USR023',
action: 'Password changed',
details: 'Security credentials updated',
timestamp: '2025-10-16 14:15:30',
status: 'success',
icon: <Lock />
},
{
id: 5,
type: 'login',
user: 'david_lee',
userId: 'USR056',
action: 'User login',
details: 'IP: 192.168.1.100, Location: New York, USA',
timestamp: '2025-10-16 14:10:15',
status: 'success',
icon: <User />
},
{
id: 6,
type: 'game_play',
user: 'lisa_garcia',
userId: 'USR089',
action: 'Game played',
details: 'Super Stakes Poker - Bet: $50, Won: $125',
timestamp: '2025-10-16 14:05:00',
status: 'success',
icon: <User />
},
{
id: 7,
type: 'withdrawal',
user: 'alex_martinez',
userId: 'USR034',
action: 'Withdrawal failed',
details: 'Amount: $1000.00 - Insufficient funds',
timestamp: '2025-10-16 14:00:45',
status: 'failed',
icon: <User />
},
{
id: 8,
type: 'profile_update',
user: 'rachel_white',
userId: 'USR067',
action: 'Profile updated',
details: 'Changed email and phone number',
timestamp: '2025-10-16 13:55:30',
status: 'success',
icon: <User />
},
{
id: 9,
type: 'bonus',
user: 'chris_taylor',
userId: 'USR045',
action: 'Bonus claimed',
details: 'Welcome Bonus: $100 credited',
timestamp: '2025-10-16 13:50:15',
status: 'success',
icon: <User />
},
{
id: 10,
type: 'logout',
user: 'olivia_anderson',
userId: 'USR091',
action: 'User logout',
details: 'Session ended normally',
timestamp: '2025-10-16 13:45:00',
status: 'success',
icon: <User />
},
{
id: 11,
type: 'deposit',
user: 'james_thomas',
userId: 'USR012',
action: 'Cash deposit',
details: 'Amount: $1,200.00 via Bank Transfer',
timestamp: '2025-10-16 13:40:45',
status: 'success',
icon: <User />
},
{
id: 12,
type: 'password_update',
user: 'sophia_jackson',
userId: 'USR073',
action: 'Password reset',
details: 'Reset via email verification',
timestamp: '2025-10-16 13:35:30',
status: 'success',
icon: <Lock />
}
];
const [search, setSearch] = React.useState("");
const [page, setPage] = React.useState(1);
......@@ -175,22 +33,21 @@ export default function Activities() {
const [activityType, setActivityType] = React.useState("all");
const [sorting, setSorting] = React.useState<any>([]);
const queryArgs = useMemo(
() => ({
page,
per_page: pageSize,
search: search || "",
activity_type: activityType,
status
}),
[page, pageSize, search, status]
);
const filteredData = useMemo(() => {
return activities.filter(activity => {
const matchesSearch = search === "" ||
activity.user.toLowerCase().includes(search.toLowerCase()) ||
activity.userId.toLowerCase().includes(search.toLowerCase()) ||
activity.action.toLowerCase().includes(search.toLowerCase()) ||
activity.details.toLowerCase().includes(search.toLowerCase());
const matchesType = activityType === "all" || activity.type === activityType;
return matchesSearch && matchesType;
});
}, [search, activityType]);
const { data, isLoading } = useGetAllActivityQuery(queryArgs)
const columns = useMemo<ColumnDef<Activity>[]>(() => [
const columns = useMemo<ColumnDef<ActivityProps>[]>(() => [
{
accessorKey: "id",
header: ({ column }) => {
......@@ -216,23 +73,16 @@ export default function Activities() {
accessorKey: "user",
header: "User",
cell: ({ row }) => {
const { user, userId } = row.original;
const initials = user.split('_').map(n => n[0]).join('').toUpperCase();
const { username, email } = row.original
return (
<Box className="flex justify-start items-center gap-2">
<small className="text-[10px] w-[24px] h-[24px] flex items-center justify-center uppercase rounded-[4px] bg-[#1EB41B]/10 font-[500] text-[#1EB41B]">
{initials}
<div className="name-detail">
<strong className="text-primary block text-[12px] leading-[120%] font-[500] capitalize">
{username}
</strong>
<small className="text-[10px] text-para-light font-[500]">
{email}
</small>
<div className="name-detail">
<strong className="text-primary block text-[12px] leading-[120%] font-[500] capitalize">
{user.replace('_', ' ')}
</strong>
<small className="text-[10px] text-para-light font-[500]">
{userId}
</small>
</div>
</Box>
</div>
);
},
},
......@@ -253,18 +103,11 @@ export default function Activities() {
},
},
{
accessorKey: "action",
header: "Action",
cell: ({ row }) => (
<span className="text-[12px] font-[500]">{row.original.action}</span>
),
},
{
accessorKey: "details",
header: "Details",
cell: ({ row }) => (
<span className="text-[11px] text-para-light max-w-[250px] block truncate">
{row.original.details}
{row.original.log}
</span>
),
},
......@@ -300,7 +143,7 @@ export default function Activities() {
], []);
const table = useReactTable({
data: filteredData,
data: data?.data?.data || [],
columns,
state: { sorting },
onSortingChange: setSorting,
......@@ -309,8 +152,6 @@ export default function Activities() {
getSortedRowModel: getSortedRowModel(),
});
const totalPages = Math.ceil(filteredData.length / pageSize);
return (
<div className="border-gray border-solid border-[1px] rounded-[8px] lg:rounded-[16px]">
......@@ -332,15 +173,12 @@ export default function Activities() {
/>
<div className="flex flex-col md:flex-row justify-between items-start md:items-center mt-4 px-8 py-6 gap-4">
<div className="flex items-center gap-2">
<span className="text-[12px] font-[500]">Rows per page:</span>
<div>
<span>Row per page:</span>
<select
value={pageSize}
onChange={(e) => {
setPageSize(Number(e.target.value));
setPage(1);
}}
className="border border-gray-300 rounded p-1 text-[12px]"
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}>
......@@ -348,18 +186,10 @@ export default function Activities() {
</option>
))}
</select>
{/* <span className="text-[12px] text-para-light ml-4">
Showing {filteredData.length > 0 ? ((page - 1) * pageSize) + 1 : 0} to {Math.min(page * pageSize, filteredData.length)} of {filteredData.length} activities
</span> */}
</div>
<Pagination
count={totalPages || 1}
<Pagination count={data?.data?.pagination?.total_pages || 1}
page={page}
onChange={(_, value) => setPage(value)}
variant="outlined"
shape="rounded"
sx={{ gap: "8px" }}
/>
onChange={(_, value) => setPage(value)} variant="outlined" shape="rounded" sx={{ gap: "8px" }} />
</div>
</div>
);
......
......@@ -7,11 +7,11 @@ export default function ActivityLogPage() {
return (
<section className="activity__log__root">
<PageHeader title='Activity Log' />
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4 gap-4 mb-8">
{/* <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4 gap-4 mb-8">
{Array.from({ length: 4 }).map((_, index) => (
<ActivityAnalyticCard key={index.toString()} />
))}
</div>
</div> */}
<div className="section-title mb-4">
<h2 className="text-[20px] leading-[140%] font-[600]">
All Activities
......
......@@ -4,12 +4,12 @@ import { useEffect } from "react";
import { useAppDispatch } from "@/hooks/hook";
import { setTokens } from "@/slice/authSlice";
export default function ReduxHydrator({ token, user }: { token: string; user: any }) {
export default function ReduxHydrator({ token }: { token: string; }) {
const dispatch = useAppDispatch();
useEffect(() => {
dispatch(setTokens({ access_token: token, user }));
}, [dispatch, token, user]);
// useEffect(() => {
// dispatch(setTokens({ access_token: token, user }));
// }, [dispatch, token, user]);
return null;
}
......@@ -24,13 +24,13 @@ export default async function ServerPrivate({ children }: { children: React.Reac
redirect("/");
}
// Optionally, you could fetch user data from your API here if you don't store it in JWT
const user = payload; // Or fetch user profile based on token
// const user = payload;
return (
<>
{/* ✅ Hydrate Redux store on client */}
<ReduxHydrator token={access_token} user={user} />
<ReduxHydrator token={access_token} />
{children}
</>
);
......
import { createApi } from "@reduxjs/toolkit/query/react";
import { baseQuery } from "./baseQuery";
import { NotificationResponse } from "@/types/notification";
import { ActivityResponse, NotificationResponse } from "@/types/notification";
import { GlobalResponse, QueryParams } from "@/types/config";
export const notificationApi = createApi({
......@@ -36,13 +36,14 @@ export const notificationApi = createApi({
}),
invalidatesTags: ["Notification"]
}),
getAllActivity: builder.query<NotificationResponse, { activity_type: string } & QueryParams>({
query: ({ search, page, per_page, activity_type }) => {
getAllActivity: builder.query<ActivityResponse, { activity_type: string, status?: string } & QueryParams>({
query: ({ search, page, per_page, activity_type, status }) => {
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());
if (per_page) params.append('activity_type', activity_type.toString());
if (activity_type) params.append('activity_type', activity_type.toString());
if (status) params.append('status', status.toString());
const queryString = params.toString();
return {
url: `/api/admin/activity${queryString ? `?${queryString}` : ''}`,
......
......@@ -20,7 +20,18 @@ export interface ActivityResponse {
success: boolean;
message: string;
data: {
data: {}[];
data: ActivityProps[];
pagination: Pagination;
}
}
export interface ActivityProps {
id: number,
username: string,
email: string,
status: string,
log: string
type: string,
timestamp: string;
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment