Compare commits

...

19 Commits

Author SHA1 Message Date
CHIEFSOFT\ameye 37b371faf5 added data 2026-05-24 16:43:06 -04:00
CHIEFSOFT\ameye 601babbcae added data 2026-05-24 16:36:00 -04:00
ameye 7ca70d7f55 Turnstile 2026-05-23 14:50:37 -04:00
ameye 39c99f68bc Sign up reports 2026-05-02 08:52:27 -04:00
ameye 21a43b9c21 more pages 2026-05-02 02:38:47 -04:00
ameye 2b1ec53131 fix banner 2026-05-01 19:19:31 -04:00
ameye 1054fa0fd1 add acc page 2026-05-01 19:15:49 -04:00
ameye 094b8b15d5 Merge branch 'update-profile' of MERMS/MermsFirstOffice into master 2026-04-30 16:13:42 +00:00
victorAnumudu b0263ae768 update profile addded 2026-04-30 13:43:35 +01:00
ameye 0fb267e3f4 Merge branch 'account-edit-page-fixed' of MERMS/MermsFirstOffice into master 2026-04-29 18:21:29 +00:00
victorAnumudu c686acf1e8 account edit page fixed 2026-04-29 19:18:07 +01:00
ameye 5d8d5aa2be banner added 2026-04-29 12:55:01 -04:00
ameye 5c8f96d92a Account edit files 2026-04-29 12:44:21 -04:00
ameye ae0500a799 Added country 2026-04-29 11:27:55 -04:00
ameye fc38378210 recent signup 2026-04-26 20:45:18 -04:00
CHIEFSOFT\ameye 044b880726 added data 2026-01-24 11:18:35 -05:00
CHIEFSOFT\ameye 28b3d85b24 merms office 2026-01-01 15:09:22 -05:00
CHIEFSOFT\ameye f440c5a9b9 added file upload 2026-01-01 14:14:57 -05:00
ameye 4481e6b826 Merge branch 'build-bug-fix' of MERMS/MermsFirstOffice into master 2025-11-19 10:58:25 +00:00
26 changed files with 1391 additions and 307 deletions
+3 -1
View File
@@ -7,7 +7,7 @@ INSTAGRAM_URL=https://www.instagram.com
# BACKEND END POINTS
REACT_APP_MAIN_API="https://devapi.mermsemr.com"
# REACT_APP_MAIN_API="http://localhost:14700"
#REACT_APP_MAIN_API="http://localhost:14700"
# ENQUIRIES CONTACTS
VITE_CALL_ENDPOINT='09099000000'
@@ -20,4 +20,6 @@ VITE_BANK_NAME_SHORT='FCMB'
# Inactivity timeout/logout AT 10MINS
REACT_APP_TIMEOUT=600000
#CLOUDFLARE
REACT_APP_TURNSTILE_SITE_KEY=0x4AAAAAADQV82wuocFR-u5O
+4 -1
View File
@@ -18,4 +18,7 @@ VITE_BANK_NAME='First City Monument Bank'
VITE_BANK_NAME_SHORT='FCMB'
# Inactivity timeout/logout AT 10MINS
REACT_APP_TIMEOUT=600000
REACT_APP_TIMEOUT=600000
#CLOUDFLARE
REACT_APP_TURNSTILE_SITE_KEY=0x4AAAAAADQV82wuocFR-u5O
+5 -2
View File
@@ -7,7 +7,7 @@ INSTAGRAM_URL=https://www.instagram.com
# BACKEND END POINTS
REACT_APP_MAIN_API="https://api.mermsemr.com"
#REACT_APP_MAIN_API="http://localhost:14700"
# REACT_APP_MAIN_API="http://localhost:14700"
# ENQUIRIES CONTACTS
VITE_CALL_ENDPOINT='09099000000'
@@ -18,4 +18,7 @@ VITE_BANK_NAME='First City Monument Bank'
VITE_BANK_NAME_SHORT='FCMB'
# Inactivity timeout/logout AT 10MINS
REACT_APP_TIMEOUT=600000
REACT_APP_TIMEOUT=600000
#CLOUDFLARE
REACT_APP_TURNSTILE_SITE_KEY=0x4AAAAAADQV82wuocFR-u5O
+2 -2
View File
@@ -27,9 +27,9 @@ COPY . .
#RUN npm run build
RUN npm run start
# CMD [ "npm", "run", "preview" ]
##CMD [ "npm", "run", "start" ,"--", "--host"]
CMD /bin/sh ./run.sh
#CMD /bin/sh ./run.sh
+1
View File
@@ -5,6 +5,7 @@
"dependencies": {
"@reduxjs/toolkit": "^2.5.1",
"@tanstack/react-query": "^5.66.0",
"@marsidev/react-turnstile": "^1.5.2",
"apexcharts": "^4.5.0",
"axios": "^1.7.9",
"cra-template": "1.2.0",
+3
View File
@@ -3,10 +3,13 @@ const RouteLinks = {
homePage: '/',
customerPage: '/customer',
accountDetails: '/account-view/*',
accountEdit:'/account-edit/*',
subscriptionDetails: '/subscription-view/*',
subscriptions: '/subscriptions',
billings: '/billings',
files_uploads: '/files-uploads',
recentSignup: '/recent-signup',
recentAccount: '/recent-account',
loansPage: '/loans',
transactionsPage: '/transactions',
countrySettings: '/country',
+11 -4
View File
@@ -22,8 +22,10 @@ import ProductTemplatePage from "./pages/ProductTemplatePage";
import CustomTemplatePage from "./pages/CustomTemplatePage";
import SubscriptionDetailsPage from "./pages/SubscriptionDetailsPage";
import CountrySettingsPage from "./pages/CountrySettingsPage";
import ProductViewPage from "./pages/ProductViewPage"; // TRANSACTION DETAILS PAGE
import ProductViewPage from "./pages/ProductViewPage";
import FilesUploadPage from "./pages/FilesUploadPage"; // TRANSACTION DETAILS PAGE
import AccountEditpage from "./pages/AccountEditPage";
import RecentAccountsPage from "./pages/RecentAccountsPage";
// const Home = lazy(() => import('./pages/Home'));
@@ -34,22 +36,27 @@ export default function SiteRoutes() {
<Route element={<UserExist/>}>
<Route path={RouteLinks.homePage} element={<HomePage/>}/> {`*/HOME PAGE*/`}
<Route path={RouteLinks.recentAccount} element={<RecentAccountsPage/>}/> {`*/RECENT SIGNUP PAGE*/`}
<Route path={RouteLinks.recentSignup} element={<RecentSignupPage/>}/> {`*/RECENT SIGNUP PAGE*/`}
<Route path={RouteLinks.subscriptions} element={<SubscriptionsPage/>}/> {`*/SUBSCRIPTIONS PAGE*/`}
<Route path={RouteLinks.customerPage} element={<CustomerPage/>}/> {`*/CUSTOMER PAGE*/`}
<Route path={RouteLinks.accountDetails} element={<AccountDetailsPage/>}/> {`*/CUSTOMER PAGE*/`}
<Route path={RouteLinks.accountEdit} element={<AccountEditpage/>}/> {`*/CUSTOMER EDIT PAGE*/`}
<Route path={RouteLinks.billings} element={<BillingsPage/>}/> {`*/BILLINGS PAGE*/`}
<Route path={RouteLinks.products} element={<ProductsPage/>}/> {`*/PRODUCTS PAGE*/`}
<Route path={RouteLinks.productView} element={<ProductViewPage/>}/> {`*/PRODUCTS VIEW PAGE*/`}
<Route path={RouteLinks.usersAdmin} element={<UsersAdminPage/>}/> {`*/ADMIN USERS PAGE*/`}
<Route path={RouteLinks.files_uploads} element={<FilesUploadPage/>}/> {`*/FILES PAGE*/`}
<Route path={RouteLinks.countrySettings} element={<CountrySettingsPage/>}/> {`*/PRODUCTS TEMPLATE PAGE*/`}
<Route path={RouteLinks.productTemplates} element={<ProductTemplatePage/>}/> {`*/PRODUCTS TEMPLATE PAGE*/`}
<Route path={RouteLinks.customTemplates} element={<CustomTemplatePage/>}/> {`*/CUSTOM TEMPLATES PAGE*/`}
<Route path={RouteLinks.loansPage} element={<LoansPage/>}/> {`*/LOANS PAGE*/`}
<Route path={RouteLinks.transaction_details_page}
element={<TransactionDetailsPage/>}/> {`*/TRANSACTION PAGE*/`}
<Route path={RouteLinks.transaction_details_page} element={<TransactionDetailsPage/>}/> {`*/TRANSACTION PAGE*/`}
<Route path={RouteLinks.subscriptionDetails} element={<SubscriptionDetailsPage/>} />
</Route>
+2 -2
View File
@@ -1,9 +1,9 @@
import React from 'react'
export default function InputText({id, name, type='text', placeholder, value, handleChange}) {
export default function InputText({id, name, type='text', placeholder, disabled, value, handleChange}) {
return (
<div className='w-full h-12 overflow-hidden'>
<input id={id} name={name} type={type} value={value} placeholder={placeholder} onChange={handleChange} className='p-2 w-full h-full text-black outline-0 ring-0 border border-slate-300 focus:border-purple-600 rounded-md' />
<input id={id} name={name} type={type} value={value} placeholder={placeholder} disabled={disabled} onChange={handleChange} className='p-2 w-full h-full text-black outline-0 ring-0 border border-slate-300 focus:border-purple-600 rounded-md' />
</div>
)
}
@@ -0,0 +1,89 @@
import {Link} from 'react-router-dom'
import Icons from "../Icons";
export default function AccountBannerCom({accountInfo}) {
return <>
<div className="pb-5">
<div className="flex flex-col gap-1">
<div className="w-full">
<h4 className="font-semibold text-lg">Account Info</h4>
</div>
<div className="w-full overflow-x-auto">
<>
<table className="py-2 w-full text-sm bg-[aliceblue] dark:bg-transparent rounded-[10px]">
<thead className="py-2 text-sm text-slate-500 text-left">
<tr>
<th scope="col" className="px-2 py-2">
Firstname
</th>
<th scope="col" className="px-2">
Lastname
</th>
<th scope="col" className="px-2">
Username
</th>
<th scope="col" className="px-2">
Email
</th>
<th scope="col" className="px-2">
Country
</th>
</tr>
</thead>
<tbody>
{(accountInfo && Object.keys(accountInfo).length > 0) ?
<tr className="py-2 border-t border-dashed border-slate-300">
{/*<td className="px-2 py-2">*/}
{/* <div className="text-left">*/}
{/* <div className="text-base font-semibold">{1}</div>*/}
{/* </div>*/}
{/*</td>*/}
<td className="px-2">
<div className="text-left">
<div className="text-base font-semibold">{accountInfo?.firstname}</div>
</div>
</td>
<td className="px-2">
<div className="text-left">
<div className="text-base font-semibold">{accountInfo?.lastname}</div>
</div>
</td>
<td className="px-2">
<div className="text-left">
<div className="text-base font-semibold">{accountInfo?.username}</div>
</div>
</td>
<td className="px-2">
<div className="text-left">
<div className="text-base font-semibold">{accountInfo?.email}</div>
</div>
</td>
<td className="px-2">
<div className="text-left">
<div className="text-base font-semibold">{accountInfo?.country}</div>
</div>
</td>
</tr>
:
<tr className="py-2 border-t border-dashed border-slate-300">
<td className="px-3 py-2" colSpan={6}>
<div className="flex justify-center items-center">
No Record Found
</div>
</td>
</tr>
}
</tbody>
</table>
</>
</div>
</div>
</div>
</>
}
@@ -0,0 +1,89 @@
import { useEffect } from "react";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { useLocation, useNavigate } from "react-router-dom";
import BreadcrumbCom from "../breadcrumb/BreadcrumbCom";
import RouteLinks from "../../RouteLinks";
import { getAccountView } from "../../services/siteServices";
import queryKeys from "../../services/queryKeys";
import AccountBannerCom from "./AccountBannerCom";
import UpdateProfile from "./UpdateProfile";
import UpdatePassword from "./UpdatePassword";
export default function AccountEditCom() {
const queryClient = useQueryClient();
const { state } = useLocation();
const navigate = useNavigate();
useEffect(() => {
if (!state?.memberUID) {
navigate(RouteLinks.homePage, { replace: true });
}
}, [navigate, state?.memberUID]);
const { data, isFetching, isError, error } = useQuery({
queryKey: queryKeys.account_view,
queryFn: () => {
// const filterData = filter?.type ? {[filter?.type]: filter.id} : {}
const reqData = {
member_uid: state?.memberUID,
// page,
// ...filterData
};
return getAccountView(reqData);
},
staleTime: 0, //0 mins
});
const accountsViewData = data?.data; // ACCOUNT EDIT DATA
const account_info = accountsViewData?.account;
useEffect(() => {
queryClient.refetchQueries({
queryKey: [...queryKeys.account_view],
// type: 'active',
// exact: true,
});
}, [queryClient, state?.memberUID]);
return (
<div className="w-full flex flex-col gap-8">
<BreadcrumbCom
title={`Account Edit [${state?.memberUID}]`}
paths={["Dashboard", "Account Edit"]}
/>
{isFetching ? (
<>
<div className="box bg-white dark:bg-black-box text-black-body dark:text-white-body">
<p className="text-slate-800">Loading...</p>
</div>
</>
) : isError ? (
<div className="box bg-white dark:bg-black-box text-black-body dark:text-white-body">
<p className="text-red-500">{error.message}</p>
</div>
) : (
<>
<div className="box bg-white dark:bg-black-box text-black-body dark:text-white-body">
<AccountBannerCom accountInfo={account_info} />
</div>
<div className="box bg-white dark:bg-black-box text-black-body dark:text-white-body">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-12 gap-5">
<div className="p-4 col-span-12 lg:col-span-7 xl:col-span-8 shadow border rounded-xl">
<UpdateProfile accountInfo={account_info} />
</div>
<div className="p-4 max-h-96 col-span-12 lg:col-span-5 xl:col-span-4 shadow border rounded-xl">
<UpdatePassword />
</div>
</div>
</div>
</>
)}
</div>
);
}
@@ -0,0 +1,124 @@
import {Link} from 'react-router-dom'
import Icons from "../Icons";
import {useMutation} from '@tanstack/react-query'
import {Formik, Form} from 'formik'
import * as Yup from "yup";
import InputText from '../InputText'
export default function UpdatePassword() {
const initialValues = {
newPassword: "",
confirmPassword: ""
};
// To get the validation schema
const validationSchema = Yup.object().shape({
newPassword: Yup.string()
.min(6, 'must be upto 6 characters')
.max(25, 'must not exceed 25 characters')
.required("New password is required"),
confirmPassword: Yup.string()
.oneOf([Yup.ref("newPassword"), null], "Passwords do not match")
.required("Confirm password is required"),
});
const updatePassword = useMutation({
mutationFn: (fields) => {
if (!fields.newPassword || !fields.confirmPassword) {
throw new Error('Please provide all fields marked *')
}
// return loginUser(fields)
},
onError: (error) => {
console.log(error)
},
onSuccess: (res) => {
// const {jwt_token, user} = res?.data
setTimeout(() => {
updatePassword.reset()
}, 3000);
}
})
//FUNCTION TO HANDLE LOGIN
const handleSubmit = (values, helper) => {
updatePassword.mutate(values)
};
return <>
<div className="flex flex-col gap-1">
<div className="w-full">
<h4 className="mb-3 font-semibold text-lg">Change Password</h4>
</div>
<div className="w-full">
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={handleSubmit}
>
{(props) => (
<Form>
<div className='flex flex-col gap-8 w-full'>
<div className='flex flex-col gap-6'>
<div className='relative text-input flex flex-col gap-2'>
<p className='flex items-center gap-1'>
<label className='text-lg'>New Password</label>
<span className='text-red-500 text-10'>{(props.errors.newPassword && props.touched.newPassword) ? props.errors.newPassword : ''}</span>
</p>
<InputText
id='newPassword'
placeholder='New Password'
name='newPassword'
type='password'
value={props.values.newPassword}
handleChange={props.handleChange}
/>
</div>
<div className='relative text-input flex flex-col gap-2'>
<p className='flex items-center gap-1'>
<label className='text-lg'>Confirm Password</label>
<span className='text-red-500 text-10'>{(props.errors.confirmPassword && props.touched.confirmPassword) ? props.errors.confirmPassword : ''}</span>
</p>
<InputText
id='confirmPassword'
placeholder='Confirm Password'
name='confirmPassword'
type='password'
value={props.values.confirmPassword}
handleChange={props.handleChange}
/>
</div>
<div className='flex justify-end h-10 mb-10'>
<button type='submit' disabled={updatePassword.isPending} className='px-4 h-full bg-primary text-white font-bold rounded-md'>{updatePassword.isPending ? 'loading...' : 'Update Password'}</button>
</div>
{updatePassword.error &&
<>
<div className="w-full text-center">
<p className='text-red-500 text-sm'>{updatePassword.error.message}</p>
</div>
</>
}
{updatePassword.success &&
<>
<div className="w-full text-center">
<p className='text-emerald-500 text-sm'>{'Password Updated Successfully'}</p>
</div>
</>
}
</div>
</div>
</Form>
)}
</Formik>
</div>
</div>
</>
}
@@ -0,0 +1,204 @@
import {Link} from 'react-router-dom'
import Icons from "../Icons";
import {useMutation} from '@tanstack/react-query'
import {Formik, Form} from 'formik'
import * as Yup from "yup";
import InputText from '../InputText'
import { useEffect } from 'react';
import { useQuery, useQueryClient } from "@tanstack/react-query";
import queryKeys from '../../services/queryKeys';
import {updateUserProfile} from '../../services/siteServices'
export default function UpdateProfile({accountInfo}) {
const queryClient = useQueryClient();
const initialValues = {
firstname: accountInfo?.firstname || "",
lastname: accountInfo?.lastname || "",
account_name: accountInfo?.account_name || "",
phone_number: accountInfo?.phone_number || "",
email: accountInfo?.email || "",
address: accountInfo?.address || "",
};
// To get the validation schema
const validationSchema = Yup.object().shape({
firstname: Yup.string().required("First name is required").min(3, 'must be upto 3 characters').max(25, 'must not exceed 25 characters'),
lastname: Yup.string().required("Last name is required").min(3, 'must be upto 3 characters').max(25, 'must not exceed 25 characters'),
account_name: Yup.string().required("Account name is required").min(3, 'must be upto 3 characters').max(25, 'must not exceed 25 characters'),
phone_number: Yup.string().required("Phone number is required").min(6, 'must be upto 6 characters').max(25, 'must not exceed 25 characters'),
email: Yup.string().email('Invalid email format').required("Email is required").max(50, 'must not exceed 50 characters'),
address: Yup.string().required("Address is required").min(10, 'must be at least 10 characters').max(255, 'must not exceed 255 characters'),
});
const updateProfile = useMutation({
mutationFn: (fields) => {
return updateUserProfile(fields)
},
onError: (error) => {
console.log(error)
},
onSuccess: (res) => {
// if(res?.data?.resultCode != '0') {
// throw new Error(res?.data?.message || res?.data?.resultDescription || 'An error occurred while updating profile')
// }
queryClient.refetchQueries({
queryKey: [...queryKeys.account_view],
// type: 'active',
// exact: true,
});
},
onSettled: () => {
setTimeout(() => {
updateProfile.reset()
}, 3000);
}
})
//FUNCTION TO HANDLE LOGIN
const handleSubmit = (values, helper) => {
const newValues= {
member_uid: accountInfo?.uid || "",
member_account_name: values?.account_name || "",
member_firstname: values?.firstname || "",
member_full_address: values?.address || "",
member_lastname: values?.lastname || "",
member_email: values?.email || "",
member_phone: values?.phone_number || ""
}
updateProfile.mutate(newValues)
console.log('values', newValues)
};
return <>
<div className="flex flex-col gap-1">
<div className="w-full">
<h4 className="mb-3 font-semibold text-lg">Edit Your Personal Settings</h4>
</div>
<div className="w-full">
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={handleSubmit}
>
{(props) => (
<Form>
<div className='flex flex-col gap-8 w-full'>
<div className='flex flex-col gap-6'>
<div className='relative text-input flex flex-col gap-2'>
<p className='flex items-center gap-1'>
<label className='text-lg'>First Name</label>
<span className='text-red-500 text-10'>{(props.errors.firstname && props.touched.firstname) ? props.errors.firstname : ''}</span>
</p>
<InputText
id='firstname'
placeholder='First Name'
name='firstname'
value={props.values.firstname}
handleChange={props.handleChange}
/>
</div>
<div className='relative text-input flex flex-col gap-2'>
<p className='flex items-center gap-1'>
<label className='text-lg'>Last Name</label>
<span className='text-red-500 text-10'>{(props.errors.lastname && props.touched.lastname) ? props.errors.lastname : ''}</span>
</p>
<InputText
id='lastname'
placeholder='Last Name'
name='lastname'
value={props.values.lastname}
handleChange={props.handleChange}
/>
</div>
<div className='relative text-input flex flex-col gap-2'>
<p className='flex items-center gap-1'>
<label className='text-lg'>Account Name</label>
<span className='text-red-500 text-10'>{(props.errors.account_name && props.touched.account_name) ? props.errors.account_name : ''}</span>
</p>
<InputText
id='account_name'
placeholder='Account Name'
name='account_name'
value={props.values.account_name}
handleChange={props.handleChange}
/>
</div>
<div className='relative text-input flex flex-col gap-2'>
<p className='flex items-center gap-1'>
<label className='text-lg'>Phone Number</label>
<span className='text-red-500 text-10'>{(props.errors.phone_number && props.touched.phone_number) ? props.errors.phone_number : ''}</span>
</p>
<InputText
id='phone_number'
placeholder='Phone Number'
name='phone_number'
value={props.values.phone_number}
handleChange={props.handleChange}
/>
</div>
<div className='relative text-input flex flex-col gap-2'>
<p className='flex items-center gap-1'>
<label className='text-lg'>Email</label>
<span className='text-red-500 text-10'>{(props.errors.email && props.touched.email) ? props.errors.email : ''}</span>
</p>
<InputText
id='email'
placeholder='Email'
disabled={true}
name='email'
value={props.values.email}
handleChange={props.handleChange}
/>
</div>
<div className='relative text-input flex flex-col gap-2'>
<p className='flex items-center gap-1'>
<label className='text-lg'>Address</label>
<span className='text-red-500 text-10'>{(props.errors.address && props.touched.address) ? props.errors.address : ''}</span>
</p>
<InputText
id='address'
placeholder='Address'
name='address'
value={props.values.address}
handleChange={props.handleChange}
/>
</div>
<div className='flex justify-end h-10 mb-10'>
<button type='submit' disabled={updateProfile.isPending} className='px-4 h-full bg-primary text-white font-bold rounded-md'>{updateProfile.isPending ? 'loading...' : 'Update Profile'}</button>
</div>
{updateProfile.error &&
<>
<div className="w-full text-center">
<p className='text-red-500 text-sm'>{updateProfile.error.message}</p>
</div>
</>
}
{updateProfile.error &&
<>
<div className="w-full text-center">
<p className='text-emerald-500 text-sm'>{'Profile Updated Successfully'}</p>
</div>
</>
}
</div>
</div>
</Form>
)}
</Formik>
</div>
</div>
</>
}
+65 -66
View File
@@ -1,80 +1,79 @@
import {useEffect} from 'react'
import { useQuery, useQueryClient } from '@tanstack/react-query'
import { useEffect } from "react";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import {useLocation, useNavigate} from 'react-router-dom'
import { useLocation, useNavigate } from "react-router-dom";
import BreadcrumbCom from '../breadcrumb/BreadcrumbCom'
import RouteLinks from '../../RouteLinks'
import { getAccountView } from '../../services/siteServices'
import queryKeys from '../../services/queryKeys'
import BreadcrumbCom from "../breadcrumb/BreadcrumbCom";
import RouteLinks from "../../RouteLinks";
import { getAccountView } from "../../services/siteServices";
import queryKeys from "../../services/queryKeys";
import CustomerAccountView from "./CustomerAccountView";
import AccountProfileView from "./AccountProfileView";
import CustomerSubscriptionsView from "./CustomerSubscriptionsView";
import CustomerPaymentsView from "./CustomerPaymentsView";
export default function AccountViewCom() {
const queryClient = useQueryClient();
const queryClient = useQueryClient()
const { state } = useLocation();
const navigate = useNavigate();
const {state} = useLocation()
const navigate = useNavigate()
useEffect(() => {
if (!state?.memberUID) {
navigate(RouteLinks.homePage, { replace: true });
}
}, [navigate, state?.memberUID]);
useEffect(()=>{
if(!state?.memberUID){
navigate(RouteLinks.homePage, {replace: true})
}
},[navigate, state?.memberUID])
const { data, isFetching, isError, error } = useQuery({
queryKey: queryKeys.account_view,
queryFn: () => {
// const filterData = filter?.type ? {[filter?.type]: filter.id} : {}
const reqData = {
member_uid: state?.memberUID,
// page,
// ...filterData
};
return getAccountView(reqData);
},
staleTime: 0, //0 mins
});
const accountsViewData = data?.data; // ACCOUNT VIEW DATA
const account_info = accountsViewData?.account;
const account_profile = accountsViewData?.account_profile;
const subscriptions = accountsViewData?.subscriptions;
const payments = accountsViewData?.payments;
const {data, isFetching, isError, error} = useQuery({
queryKey: queryKeys.account_view,
queryFn: () => {
// const filterData = filter?.type ? {[filter?.type]: filter.id} : {}
const reqData = {
member_uid: state?.memberUID
// page,
// ...filterData
}
return getAccountView(reqData)
},
staleTime: 0 //0 mins
})
const accountsViewData = data?.data // ACCOUNT VIEW DATA
const account_info = accountsViewData?.account
const account_profile = accountsViewData?.account_profile
const subscriptions = accountsViewData?.subscriptions
const payments = accountsViewData?.payments
// console.log('DATA', payments, subscriptions)
useEffect(() => {
queryClient.refetchQueries({
queryKey: [...queryKeys.account_view],
// type: 'active',
// exact: true,
});
}, [queryClient, state?.memberUID]);
useEffect(()=>{
queryClient.refetchQueries({
queryKey: [...queryKeys.account_view],
// type: 'active',
// exact: true,
})
},[queryClient, state?.memberUID])
return (
<div className="w-full flex flex-col gap-8">
<BreadcrumbCom
title={`Account View [${state?.memberUID}]`}
paths={["Dashboard", "Account View"]}
/>
return (
<div className='w-full flex flex-col gap-8'>
<BreadcrumbCom title={`Account View [${state?.memberUID}]`} paths={['Dashboard', 'Account View']}/>
<div className='box bg-white dark:bg-black-box text-black-body dark:text-white-body'>
{isFetching ?
<>
<p className='text-slate-800'>Loading...</p>
</>
: isError ?
<p className='text-red-500'>{error.message}</p>
:
<>
<CustomerAccountView accountInfo={account_info} />
<AccountProfileView profile={account_profile} />
<CustomerSubscriptionsView subscriptions={subscriptions} />
<CustomerPaymentsView payments={payments} />
</>
}
</div>
</div>
)
}
<div className="box bg-white dark:bg-black-box text-black-body dark:text-white-body">
{isFetching ? (
<>
<p className="text-slate-800">Loading...</p>
</>
) : isError ? (
<p className="text-red-500">{error.message}</p>
) : (
<>
<CustomerAccountView accountInfo={account_info} />
<AccountProfileView profile={account_profile} />
<CustomerSubscriptionsView subscriptions={subscriptions} />
<CustomerPaymentsView payments={payments} />
</>
)}
</div>
</div>
);
}
@@ -1,5 +1,6 @@
import {Link} from 'react-router-dom'
import Icons from "../Icons";
import RouteLinks from '../../RouteLinks';
export default function CustomerAccountView({accountInfo}) {
return <>
@@ -26,7 +27,9 @@ export default function CustomerAccountView({accountInfo}) {
<th scope="col" className="px-2">
Email
</th>
<th scope="col" className="px-2">
Country
</th>
<th scope="col" className="px-2 text-right">
Action
</th>
@@ -60,13 +63,21 @@ export default function CustomerAccountView({accountInfo}) {
<div className="text-base font-semibold">{accountInfo?.email}</div>
</div>
</td>
<td className="px-2">
<div className="text-left">
<div className="text-base font-semibold">{accountInfo?.country}</div>
</div>
</td>
<td className="px-2 text-right">
<div className='flex items-center justify-end gap-3 md:gap-4'>
<div
className='p-2 flex justify-center items-center text-slate-500 bg-white-body dark:text-white-body dark:bg-black-body rounded-md'>
<Link
// to={`/subscription-view/63554d40-9ba1-4afe-80c2-ca147236f7ee`}
to={`/account-edit/${accountInfo?.uid}`}
state={{
customerID: accountInfo?.id,
memberUID: accountInfo?.uid,
}}
>
<Icons name='eye'/>
</Link>
+15 -5
View File
@@ -4,6 +4,7 @@ import {useNavigate} from 'react-router-dom'
import {useMutation} from '@tanstack/react-query'
import {Formik, Form} from 'formik'
import * as Yup from "yup";
import {Turnstile} from '@marsidev/react-turnstile'
import InputText from '../InputText'
import {updateUserDetails} from "../../store/UserDetails";
@@ -30,6 +31,7 @@ export default function LoginCom() {
const navigate = useNavigate()
const [loading, setLoading] = useState(false)
const [turnstileToken, setTurnstileToken] = useState(null)
const login = useMutation({
mutationFn: (fields) => {
@@ -53,9 +55,9 @@ export default function LoginCom() {
})
//FUNCTION TO HANDLE LOGIN
const handleSubmit = (values, helper) => {
login.mutate(values)
// handleLogin()
const handleSubmit = (values) => {
if (!turnstileToken) return
login.mutate({...values, cf_turnstile_response: turnstileToken})
};
return (
@@ -107,9 +109,17 @@ export default function LoginCom() {
/>
{/* <p className='text-sm text-end font-medium text-primary'>Forget password ?</p> */}
</div>
<div className="text-center col-12 mt-3">
<Turnstile
siteKey={process.env.REACT_APP_TURNSTILE_SITE_KEY}
onSuccess={setTurnstileToken}
onExpire={() => setTurnstileToken(null)}
onError={() => setTurnstileToken(null)}
/>
</div>
<div className='h-10 mb-10'>
<button type='submit' disabled={login.isPending}
className='w-full h-full bg-primary text-white font-bold rounded-md'>{login.isPending || loading ? 'loading...' : 'Login'}</button>
<button type='submit' disabled={login.isPending || !turnstileToken}
className='w-full h-full bg-primary text-white font-bold rounded-md disabled:opacity-50 disabled:cursor-not-allowed'>{login.isPending || loading ? 'loading...' : 'Login'}</button>
</div>
{login.error &&
@@ -0,0 +1,175 @@
import {useState} from 'react'
import {useQuery} from '@tanstack/react-query'
import queryKeys from '../../services/queryKeys'
import BreadcrumbCom from '../breadcrumb/BreadcrumbCom'
import TablePaginatedWrapper from '../tableWrapper/TablePaginatedWrapper'
import Icons from '../Icons'
import {getFilesUpload} from '../../services/siteServices'
import getDateTimeFromDateString from '../../helpers/getDateTimeFromDateString';
export default function FileUploadCom() {
const [page, setPage] = useState(1)
const [filter, setFilter] = useState({type: '', id: ''})
const [willFilter, setWillFilter] = useState(false)
const handleFilter = ({target: {name, value}}) => {
setFilter(prev => ({...prev, [name]: value}))
}
const handleFilterByParams = () => {
if (filter.type && !filter.id) {
return
} else if (!filter.type) {
setPage(1)
setWillFilter(prev => !prev)
setFilter({type: '', id: ''})
} else {
setPage(1)
setWillFilter(prev => !prev)
}
}
const {data, isFetching, isError, error} = useQuery({
queryKey: [...queryKeys.billings, page, willFilter],
queryFn: () => {
const filterData = filter?.type ? {[filter?.type]: filter.id} : {}
const reqData = {
page,
...filterData
}
return getFilesUpload(reqData)
},
staleTime: 0 //0 mins
})
const billingData = data?.data?.file_list; // file_list
const media_server = data?.data?.media_server; // file_list
const pagination = data?.data?.pagination
debugger;
return (
<div className='w-full flex flex-col gap-8'>
<BreadcrumbCom title='Files' paths={['Dashboard', 'Files']}/>
<div className='box bg-white dark:bg-black-box text-black-body dark:text-white-body'>
{isError ?
<p className='text-red-500'>{error?.message}</p>
:
<>
{/* filter section */}
<div className='px-2 py-2 mb-4 flex flex-col sm:flex-row flex-wrap sm:items-center gap-2'>
<Icons name='filter' className='text-3xl'/>
<div className='w-full sm:max-w-48'>
<select name='type' value={filter?.type} className='h-10 w-full p-2 rounded-md'
onChange={handleFilter}>
<option value=''>All</option>
<option value='file_group'>File Group</option>
<option value='member_id'>Member ID</option>
</select>
</div>
<div className='w-full sm:max-w-48'>
<input name='id' value={filter?.id} disabled={!filter.type}
className={`h-10 w-full p-2 rounded-md outline-none border border-black-aside ${!filter.type && 'opacity-30'}`}
onChange={handleFilter}/>
</div>
<button onClick={handleFilterByParams} disabled={filter.type && !filter.id}
className={`h-10 bg-primary px-2 py-1 rounded-md text-white font-medium sm:self-end ${(filter.type && !filter.id) && 'opacity-50'}`}>Submit
</button>
</div>
{/* end of filter section */}
<TablePaginatedWrapper data={billingData} isFetching={isFetching} setPage={setPage}
itemsPerPage={pagination?.limit} pagination={pagination}>
{({data}) => (
<>
<table className="py-2 w-full text-sm">
<thead className="py-2 text-sm text-slate-500 text-left">
<tr>
<th scope="col" className="px-2 py-2">
Added
</th>
<th scope="col" className="px-2">
Member ID
</th>
<th scope="col" className="px-2">
File Name
</th>
<th scope="col" className="px-2 text-right">
Group
</th>
<th scope="col" className="px-2 text-right">
Status
</th>
</tr>
</thead>
<tbody>
{(data && data.length > 0) ? data?.map((item, index) => {
const file_url = (media_server + "/" + item?.file_group + "/" + item?.file_uid + "/" + item.filename).toLowerCase();
return (
<tr key={index}
className="py-2 border-t border-dashed border-slate-300">
<td className="px-2 py-2">
<div
className='w-full min-w-48 flex items-center gap-2 whitespace-nowrap'>
<div className="text-left">
<div
className="text-base font-semibold">{getDateTimeFromDateString(item?.added)}</div>
</div>
</div>
</td>
<td className="px-2">
<div className="text-left">
<div
className="text-base font-semibold">{item?.member_id}</div>
</div>
</td>
<td className="px-2">
<div className="text-left">
<div
className="text-base font-semibold">
<a href={`${file_url}`} target='_blank'
rel="noreferrer">
{item?.filename}
</a>
</div>
</div>
</td>
<td className="px-2">
<div className="text-right">
<div
className="text-base font-semibold">{item?.file_group}</div>
</div>
</td>
<td className="px-2">
<div className="text-right">
<div
className="text-base font-semibold">{item?.status}</div>
</div>
</td>
</tr>
)
}
)
:
<tr className="py-2 border-t border-dashed border-slate-300">
<td className="px-3 py-2" colSpan={4}>
<div className="flex justify-center items-center">
No Record Found
</div>
</td>
</tr>
}
</tbody>
</table>
</>
)
}
</TablePaginatedWrapper>
</>
}
</div>
</div>
)
}
@@ -1,5 +1,5 @@
import {useLocation} from 'react-router-dom'
import { useSelector } from "react-redux";
import {useSelector} from "react-redux";
import RouteLinks from "../../../RouteLinks";
import DummyLogo from "../../DummyLogo";
import MainBtn from "../../MainBtn";
@@ -17,7 +17,7 @@ export default function DashboardAside() {
const {setLogoutModal, handleActiveMenu} = GeneralLayoutContext()
const {userDetails:{username, email}} = useSelector((state) => state.userDetails) // GETS LOGGED IN USER
const {userDetails: {username, email}} = useSelector((state) => state.userDetails) // GETS LOGGED IN USER
// const {role}= userDetails
return (
@@ -151,11 +151,13 @@ export default function DashboardAside() {
const asideNavLinks = [
{name: 'Dashboard', status: 1, icon: 'dashboard', to: RouteLinks.homePage},
{
name: 'Deployments', title: 'Activities', status: 1, icon: 'arrow-right', subLinks: [
name: 'Front Office', title: 'Activities', status: 1, icon: 'arrow-right', subLinks: [
{name: 'Recent Signup', status: 1, icon: 'dot', to: RouteLinks.recentSignup},
{name: 'Recent Accounts(30)', status: 1, icon: 'dot', to: RouteLinks.recentAccount},
{name: 'Provisions', status: 1, icon: 'dot', to: RouteLinks.subscriptions},
{name: 'Customers', status: 1, icon: 'dot', to: RouteLinks.customerPage},
{name: 'Billings', status: 1, icon: 'dot', to: RouteLinks.billings},
{name: 'Files & Uploads', status: 1, icon: 'dot', to: RouteLinks.files_uploads},
{
name: 'Configurations', status: 1, icon: 'arrow-right', subLinks: [
{name: 'Country', status: 1, icon: 'dot', to: RouteLinks.countrySettings},
@@ -1,86 +1,144 @@
import React from 'react'
import {Link} from 'react-router-dom'
import Img from '../../../assets/user_avatar.jpg'
import CustomCounter from '../../CustomCounter'
import React from "react";
import { Link } from "react-router-dom";
import Img from "../../../assets/user_avatar.jpg";
import CustomCounter from "../../CustomCounter";
export default function RecentPaymentsBar({data, isFetching, isError, error}) {
const recentPayment = data?.data?.recent_payment_summary
const recentLogin = data?.data?.recent_login
export default function RecentPaymentsBar({
data,
isFetching,
isError,
error,
}) {
const recentPayment = data?.data?.recent_payment_summary;
const recentLogin = data?.data?.recent_login;
const systemList = data?.data?.system_url;
return (
<div className='h-full p-2 sm:p-4 large:p-8 flex flex-col gap-16 aside-scroll-design'>
<div className='flex flex-col gap-4'>
<p className='text-base text-white-body font-bold'>Recent Payments [7 days]</p>
{isFetching ?
<div className='w-full flex justify-center'>
<div className="w-6 h-6 border-2 border-gray-300 border-b-gray-500 rounded-full animate-spin"></div>
</div>
:
isError ?
<p className='text-base text-white-body font-bold'>{error?.message}</p>
:
<div className='grid grid-cols-2 gap-4 sm:gap-6 large:gap-8'>
<div className='p-2 sm:p-3 large:p-4 flex flex-col border border-slate-500 border-dashed'>
<p className='text-base font-bold text-white-body'>
<CustomCounter targetNumber={recentPayment?.approved} timeInSeconds={1} />
</p>
<p className='text-sm text-slate-500'>Approved</p>
</div>
<div className='p-2 sm:p-3 large:p-4 flex flex-col border border-slate-500 border-dashed'>
<p className='text-base font-bold text-white-body'>
<CustomCounter targetNumber={recentPayment?.verified} timeInSeconds={1} />
</p>
<p className='text-sm text-slate-500'>Verified</p>
</div>
<div className='p-2 sm:p-3 large:p-4 flex flex-col border border-slate-500 border-dashed'>
<p className='text-base font-bold text-white-body'>
<CustomCounter targetNumber={recentPayment?.failed} timeInSeconds={1} />
</p>
<p className='text-sm text-slate-500'>Failed</p>
</div>
<div className='p-2 sm:p-3 large:p-4 flex flex-col border border-slate-500 border-dashed'>
<p className='text-base font-bold text-white-body'>
<CustomCounter targetNumber={recentPayment?.total} timeInSeconds={1} />
</p>
<p className='text-sm text-slate-500'>Total</p>
</div>
</div>
}
return (
<div className="h-full p-2 sm:p-4 large:p-8 flex flex-col gap-16 aside-scroll-design">
<div className="flex flex-col gap-4">
<p className="text-base text-white-body font-bold">
Recent Payments [7 days]
</p>
{isFetching ? (
<div className="w-full flex justify-center">
<div className="w-6 h-6 border-2 border-gray-300 border-b-gray-500 rounded-full animate-spin"></div>
</div>
) : isError ? (
<p className="text-base text-white-body font-bold">
{error?.message}
</p>
) : (
<div className="grid grid-cols-2 gap-4 sm:gap-6 large:gap-8">
<div className="p-2 sm:p-3 large:p-4 flex flex-col border border-slate-500 border-dashed">
<p className="text-base font-bold text-white-body">
<CustomCounter
targetNumber={recentPayment?.approved}
timeInSeconds={1}
/>
</p>
<p className="text-sm text-slate-500">Approved</p>
</div>
<div className='overflow-y-auto h-full flex flex-col gap-4'>
<p className='text-base text-white-body font-bold'>Recent Login</p>
{isFetching ?
<div className='w-full flex justify-center'>
<div className="w-6 h-6 border-2 border-gray-300 border-b-gray-500 rounded-full animate-spin"></div>
</div>
:
isError ?
<p className='text-base text-white-body font-bold'>{error?.message}</p>
:
<div className='flex flex-col gap-4'>
{recentLogin.map((item, index)=> {
return (
<Link
to={`/account-view/${item?.member_uid}`}
state={{customerID: item?.id, memberUID: item?.member_uid}}
key={index}
className='flex gap-3 items-center'
>
<div className='px-4 py-2 bg-[#0E172E] rounded-md'>
<img src={Img} className='w-8' alt="Order" />
</div>
<div className='flex-col'>
<p className='text-base font-bold text-white-body'>{item.firstname} {item.lastname}</p>
<p className='text-sm text-slate-500'>{item.username}</p>
</div>
</Link>
)
}
)}
</div>
}
<div className="p-2 sm:p-3 large:p-4 flex flex-col border border-slate-500 border-dashed">
<p className="text-base font-bold text-white-body">
<CustomCounter
targetNumber={recentPayment?.verified}
timeInSeconds={1}
/>
</p>
<p className="text-sm text-slate-500">Verified</p>
</div>
</div>
)
<div className="p-2 sm:p-3 large:p-4 flex flex-col border border-slate-500 border-dashed">
<p className="text-base font-bold text-white-body">
<CustomCounter
targetNumber={recentPayment?.failed}
timeInSeconds={1}
/>
</p>
<p className="text-sm text-slate-500">Failed</p>
</div>
<div className="p-2 sm:p-3 large:p-4 flex flex-col border border-slate-500 border-dashed">
<p className="text-base font-bold text-white-body">
<CustomCounter
targetNumber={recentPayment?.total}
timeInSeconds={1}
/>
</p>
<p className="text-sm text-slate-500">Total</p>
</div>
</div>
)}
</div>
<div className="overflow-y-auto h-full flex flex-col gap-4">
<p className="text-base text-white-body font-bold">Systems</p>
{isFetching ? (
<div className="w-full flex justify-center">
<div className="w-6 h-6 border-2 border-gray-300 border-b-gray-500 rounded-full animate-spin"></div>
</div>
) : isError ? (
<p className="text-base text-white-body font-bold">
{error?.message}
</p>
) : (
<div className="flex flex-col gap-4">
{systemList.map((item, index) => {
return (
<Link
to={item.url}
key={index}
target="_blank"
className="flex gap-3 items-center"
>
<div className="px-4 py-2 bg-[#0E172E] rounded-md">
<img src={Img} className="w-8" alt="Order" />
</div>
<div className="flex-col">
<p className="text-base font-bold text-white-body">
{item.name}
</p>
<p className="text-sm text-slate-500">{item.url}</p>
</div>
</Link>
);
})}
</div>
)}
</div>
<div className="overflow-y-auto h-full flex flex-col gap-4">
<p className="text-base text-white-body font-bold">Recent Login</p>
{isFetching ? (
<div className="w-full flex justify-center">
<div className="w-6 h-6 border-2 border-gray-300 border-b-gray-500 rounded-full animate-spin"></div>
</div>
) : isError ? (
<p className="text-base text-white-body font-bold">
{error?.message}
</p>
) : (
<div className="flex flex-col gap-4">
{recentLogin.map((item, index) => {
return (
<Link
to={`/account-view/${item?.member_uid}`}
state={{ customerID: item?.id, memberUID: item?.member_uid }}
key={index}
className="flex gap-3 items-center"
>
<div className="px-4 py-2 bg-[#0E172E] rounded-md">
<img src={Img} className="w-8" alt="Order" />
</div>
<div className="flex-col">
<p className="text-base font-bold text-white-body">
{item.firstname} {item.lastname}
</p>
<p className="text-sm text-slate-500">{item.username}</p>
</div>
</Link>
);
})}
</div>
)}
</div>
</div>
);
}
@@ -0,0 +1,203 @@
import { useState } from "react";
import { useQuery } from "@tanstack/react-query";
import queryKeys from "../../services/queryKeys";
import BreadcrumbCom from "../breadcrumb/BreadcrumbCom";
import TablePaginatedWrapper from "../tableWrapper/TablePaginatedWrapper";
import Icons from "../Icons";
import { getRecentAccount } from "../../services/siteServices";
import getDateTimeFromDateString from "../../helpers/getDateTimeFromDateString";
import {Link} from 'react-router-dom';
export default function RecentAccounts() {
const [page, setPage] = useState(1);
const [filter, setFilter] = useState({ type: "", id: "" });
const [willFilter, setWillFilter] = useState(false);
const handleFilter = ({ target: { name, value } }) => {
setFilter((prev) => ({ ...prev, [name]: value }));
};
const handleFilterByParams = () => {
if (filter.type && !filter.id) {
return;
} else if (!filter.type) {
setPage(1);
setWillFilter((prev) => !prev);
setFilter({ type: "", id: "" });
} else {
setPage(1);
setWillFilter((prev) => !prev);
}
};
const { data, isFetching, isError, error } = useQuery({
queryKey: [...queryKeys.recent_account, page, willFilter],
queryFn: () => {
const filterData = filter?.type ? { [filter?.type]: filter.id } : {};
const reqData = {
page,
...filterData,
};
return getRecentAccount(reqData);
},
staleTime: 0, //0 mins
});
const recentSignupData = data?.data?.members; // BILLINGS LIST
const pagination = data?.data?.pagination;
// console.log('DATA', data?.data)
return (
<div className="w-full flex flex-col gap-8">
<BreadcrumbCom
title="Recent Accounts"
paths={["Dashboard", "Recent Acc"]}
/>
<div className="box bg-white dark:bg-black-box text-black-body dark:text-white-body">
{isError ? (
<p className="text-red-500">{error?.message}</p>
) : (
<>
{/* filter section */}
<div className="px-2 py-2 mb-4 flex flex-col sm:flex-row flex-wrap sm:items-center gap-2">
<Icons name="filter" className="text-3xl" />
<div className="w-full sm:max-w-48">
<select
name="type"
value={filter?.type}
className="h-10 w-full p-2 rounded-md"
onChange={handleFilter}
>
<option value="">All</option>
<option value="username">Username</option>
</select>
</div>
<div className="w-full sm:max-w-48">
<input
name="id"
value={filter?.id}
disabled={!filter.type}
className={`h-10 w-full p-2 rounded-md outline-none border border-black-aside ${!filter.type && "opacity-30"}`}
onChange={handleFilter}
/>
</div>
<button
onClick={handleFilterByParams}
disabled={filter.type && !filter.id}
className={`h-10 bg-primary px-2 py-1 rounded-md text-white font-medium sm:self-end ${filter.type && !filter.id && "opacity-50"}`}
>
Submit
</button>
</div>
{/* end of filter section */}
<TablePaginatedWrapper
data={recentSignupData}
isFetching={isFetching}
setPage={setPage}
itemsPerPage={pagination?.limit}
pagination={pagination}
>
{({ data }) => (
<>
<table className="py-2 w-full text-sm">
<thead className="py-2 text-sm text-slate-500 text-left">
<tr>
<th scope="col" className="px-2 py-2">
Added
</th>
<th scope="col" className="px-2">
User Name /Email
</th>
<th scope="col" className="px-2 text-right">
Name
</th>
<th scope="col" className="px-2 text-right">
Country
</th>
<th scope="col" className="px-2 text-right">
Action
</th>
</tr>
</thead>
<tbody>
{data && data.length > 0 ? (
data?.map((item, index) => (
<tr
key={index}
className="py-2 border-t border-dashed border-slate-300"
>
<td className="px-2 py-2">
<div className="w-full min-w-48 flex items-center gap-2 whitespace-nowrap">
<div className="text-left">
<div className="text-base font-semibold">
{getDateTimeFromDateString(item?.added)}
</div>
</div>
</div>
</td>
<td className="px-2">
<div className="text-left">
<div className="text-base font-semibold">
{item?.username}
</div>
<div className="text-base font-semibold">
{item?.email}
</div>
</div>
</td>
<td className="px-2">
<div className="text-right">
<div className="text-base font-semibold">
{item?.firstname} {item?.lastname}
</div>
{/* <div className="font-normal text-gray-500">{item?.external_url}</div> */}
</div>
</td>
<td className="px-2">
<div className="text-right">
<div className="text-base font-semibold">
{item?.country}
</div>
</div>
</td>
<td className="px-2 text-right">
<div className="flex items-center justify-end gap-3 md:gap-4">
<div className="p-2 flex justify-center items-center text-slate-500 bg-white-body dark:text-white-body dark:bg-black-body rounded-md">
<Link
to={`/account-view/${item?.member_uid}`}
state={{
customerID: item?.id,
memberUID: item?.member_uid,
}}
>
<Icons name="eye" />
</Link>
</div>
</div>
</td>
</tr>
))
) : (
<tr className="py-2 border-t border-dashed border-slate-300">
<td className="px-3 py-2" colSpan={4}>
<div className="flex justify-center items-center">
No Record Found
</div>
</td>
</tr>
)}
</tbody>
</table>
</>
)}
</TablePaginatedWrapper>
</>
)}
</div>
</div>
);
}
+192 -135
View File
@@ -1,146 +1,203 @@
import { useState } from 'react'
import { useQuery } from '@tanstack/react-query'
import queryKeys from '../../services/queryKeys'
import { useState } from "react";
import { useQuery } from "@tanstack/react-query";
import queryKeys from "../../services/queryKeys";
import BreadcrumbCom from '../breadcrumb/BreadcrumbCom'
import TablePaginatedWrapper from '../tableWrapper/TablePaginatedWrapper'
import Icons from '../Icons'
import { getRecentSignup } from '../../services/siteServices'
import getDateTimeFromDateString from '../../helpers/getDateTimeFromDateString'
// import formatNumber from '../../helpers/formatNumber'
// import formatNumber from '../../helpers/formatNumber'
// import Avatar from '../../assets/user_avatar.jpg'
import BreadcrumbCom from "../breadcrumb/BreadcrumbCom";
import TablePaginatedWrapper from "../tableWrapper/TablePaginatedWrapper";
import Icons from "../Icons";
import { getRecentSignup } from "../../services/siteServices";
import getDateTimeFromDateString from "../../helpers/getDateTimeFromDateString";
import {Link} from 'react-router-dom';
export default function RecentSignup() {
const [page, setPage] = useState(1);
const [filter, setFilter] = useState({ type: "", id: "" });
const [willFilter, setWillFilter] = useState(false);
const [page, setPage] = useState(1)
const [filter, setFilter] = useState({type: '', id: ''})
const [willFilter, setWillFilter] = useState(false)
const handleFilter = ({ target: { name, value } }) => {
setFilter((prev) => ({ ...prev, [name]: value }));
};
const handleFilter = ({target:{name, value}}) => {
setFilter(prev => ({...prev, [name]:value}))
const handleFilterByParams = () => {
if (filter.type && !filter.id) {
return;
} else if (!filter.type) {
setPage(1);
setWillFilter((prev) => !prev);
setFilter({ type: "", id: "" });
} else {
setPage(1);
setWillFilter((prev) => !prev);
}
};
const handleFilterByParams = () => {
if(filter.type && !filter.id){
return
}else if(!filter.type){
setPage(1)
setWillFilter(prev => !prev)
setFilter({type: '', id: ''})
}else{
setPage(1)
setWillFilter(prev => !prev)
}
}
const { data, isFetching, isError, error } = useQuery({
queryKey: [...queryKeys.recent_signup, page, willFilter],
queryFn: () => {
const filterData = filter?.type ? { [filter?.type]: filter.id } : {};
const reqData = {
page,
...filterData,
};
return getRecentSignup(reqData);
},
staleTime: 0, //0 mins
});
const recentSignupData = data?.data?.members; // BILLINGS LIST
const pagination = data?.data?.pagination;
// console.log('DATA', data?.data)
const {data, isFetching, isError, error} = useQuery({
queryKey: [...queryKeys.recent_signup, page, willFilter],
queryFn: () => {
const filterData = filter?.type ? {[filter?.type]: filter.id} : {}
const reqData = {
page,
...filterData
}
return getRecentSignup(reqData)
},
staleTime: 0 //0 mins
})
const recentSignupData = data?.data?.payments // BILLINGS LIST
const pagination = data?.data?.pagination
// console.log('DATA', data?.data)
return (
<div className="w-full flex flex-col gap-8">
<BreadcrumbCom
title="Signup Started Report"
paths={["Dashboard", "Recent Acc"]}
/>
return (
<div className='w-full flex flex-col gap-8'>
<BreadcrumbCom title='Recent Signup' paths={['Dashboard', 'Recent Acc']} />
<div className='box bg-white dark:bg-black-box text-black-body dark:text-white-body'>
{ isError ?
<p className='text-red-500'>{error?.message}</p>
:
<>
{/* filter section */}
<div className='px-2 py-2 mb-4 flex flex-col sm:flex-row flex-wrap sm:items-center gap-2'>
<Icons name='filter' className='text-3xl' />
<div className='w-full sm:max-w-48'>
<select name='type' value={filter?.type} className='h-10 w-full p-2 rounded-md' onChange={handleFilter}>
<option value=''>All</option>
<option value='name'>Name</option>
</select>
</div>
<div className='w-full sm:max-w-48'>
<input name='id' value={filter?.id} disabled={!filter.type} className={`h-10 w-full p-2 rounded-md outline-none border border-black-aside ${!filter.type && 'opacity-30'}`} onChange={handleFilter} />
</div>
<button onClick={handleFilterByParams} disabled={filter.type && !filter.id} className={`h-10 bg-primary px-2 py-1 rounded-md text-white font-medium sm:self-end ${(filter.type && !filter.id) && 'opacity-50'}`}>Submit</button>
</div>
{/* end of filter section */}
<TablePaginatedWrapper data={recentSignupData} isFetching={isFetching} setPage={setPage} itemsPerPage={pagination?.limit} pagination={pagination}>
{({ data }) => (
<>
<table className="py-2 w-full text-sm">
<thead className="py-2 text-sm text-slate-500 text-left">
<tr>
<th scope="col" className="px-2 py-2">
Added
</th>
<th scope="col" className="px-2">
Option Name
</th>
<th scope="col" className="px-2 text-right">
Amount
</th>
<th scope="col" className="px-2 text-right">
Status
</th>
</tr>
</thead>
<tbody>
{(data && data.length > 0) ? data?.map((item, index) => (
<tr key={index} className="py-2 border-t border-dashed border-slate-300">
<td className="px-2 py-2">
<div className='w-full min-w-48 flex items-center gap-2 whitespace-nowrap'>
<div className="text-left">
<div className="text-base font-semibold">{getDateTimeFromDateString(item?.added)}</div>
</div>
</div>
</td>
<td className="px-2">
<div className="text-left">
<div className="text-base font-semibold">{item?.option_name}</div>
</div>
</td>
<td className="px-2">
<div className="text-right">
<div className="text-base font-semibold">${item?.amount}</div>
{/* <div className="font-normal text-gray-500">{item?.external_url}</div> */}
</div>
</td>
<td className="px-2">
<div className="text-right">
<div className="text-base font-semibold">{item?.status}</div>
</div>
</td>
</tr>
))
:
<tr className="py-2 border-t border-dashed border-slate-300">
<td className="px-3 py-2" colSpan={4}>
<div className="flex justify-center items-center">
No Record Found
</div>
</td>
</tr>
}
</tbody>
</table>
</>
)}
</TablePaginatedWrapper>
</>
}
<div className="box bg-white dark:bg-black-box text-black-body dark:text-white-body">
{isError ? (
<p className="text-red-500">{error?.message}</p>
) : (
<>
{/* filter section */}
<div className="px-2 py-2 mb-4 flex flex-col sm:flex-row flex-wrap sm:items-center gap-2">
<Icons name="filter" className="text-3xl" />
<div className="w-full sm:max-w-48">
<select
name="type"
value={filter?.type}
className="h-10 w-full p-2 rounded-md"
onChange={handleFilter}
>
<option value="">All</option>
<option value="username">Username</option>
</select>
</div>
<div className="w-full sm:max-w-48">
<input
name="id"
value={filter?.id}
disabled={!filter.type}
className={`h-10 w-full p-2 rounded-md outline-none border border-black-aside ${!filter.type && "opacity-30"}`}
onChange={handleFilter}
/>
</div>
<button
onClick={handleFilterByParams}
disabled={filter.type && !filter.id}
className={`h-10 bg-primary px-2 py-1 rounded-md text-white font-medium sm:self-end ${filter.type && !filter.id && "opacity-50"}`}
>
Submit
</button>
</div>
</div>
)
}
{/* end of filter section */}
<TablePaginatedWrapper
data={recentSignupData}
isFetching={isFetching}
setPage={setPage}
itemsPerPage={pagination?.limit}
pagination={pagination}
>
{({ data }) => (
<>
<table className="py-2 w-full text-sm">
<thead className="py-2 text-sm text-slate-500 text-left">
<tr>
<th scope="col" className="px-2 py-2">
Signup Stareted Added
</th>
<th scope="col" className="px-2">
Email
</th>
<th scope="col" className="px-2 text-right">
Name
</th>
<th scope="col" className="px-2 text-right">
Status
</th>
{/* <th scope="col" className="px-2 text-right">
Action
</th> */}
</tr>
</thead>
<tbody>
{data && data.length > 0 ? (
data?.map((item, index) => (
<tr
key={index}
className="py-2 border-t border-dashed border-slate-300"
>
<td className="px-2 py-2">
<div className="w-full min-w-48 flex items-center gap-2 whitespace-nowrap">
<div className="text-left">
<div className="text-base font-semibold">
{getDateTimeFromDateString(item?.added)}
</div>
</div>
</div>
</td>
<td className="px-2">
<div className="text-left">
{/* <div className="text-base font-semibold">
{item?.username}
</div> */}
<div className="text-base font-semibold">
{item?.email}
</div>
</div>
</td>
<td className="px-2">
<div className="text-right">
<div className="text-base font-semibold">
{item?.firstname} {item?.lastname}
</div>
{/* <div className="font-normal text-gray-500">{item?.external_url}</div> */}
</div>
</td>
<td className="px-2">
<div className="text-right">
<div className="text-base font-semibold">
{item?.status}
</div>
</div>
</td>
{/* <td className="px-2 text-right">
<div className="flex items-center justify-end gap-3 md:gap-4">
<div className="p-2 flex justify-center items-center text-slate-500 bg-white-body dark:text-white-body dark:bg-black-body rounded-md">
<Link
to={`/account-view/${item?.member_uid}`}
state={{
customerID: item?.id,
memberUID: item?.member_uid,
}}
>
<Icons name="eye" />
</Link>
</div>
</div>
</td> */}
</tr>
))
) : (
<tr className="py-2 border-t border-dashed border-slate-300">
<td className="px-3 py-2" colSpan={4}>
<div className="flex justify-center items-center">
No Record Found
</div>
</td>
</tr>
)}
</tbody>
</table>
</>
)}
</TablePaginatedWrapper>
</>
)}
</div>
</div>
);
}
@@ -190,8 +190,8 @@ export default function SubscriptionViewCom() {
<div className="text-base font-semibold">
<br/><span>Template :</span> {selectedSubscription?.product_template}
<br/><span>Custom :</span> {selectedSubscription?.custom_template}
<br/><span>TM :</span> - {selectedSubscription?.template_name} - {selectedSubscription?.product_template} - {selectedSubscription?.flavor}
<br/><span>CS :</span> {selectedSubscription?.custom_template}
</div>
</div>
</td>
+7
View File
@@ -0,0 +1,7 @@
import React from 'react'
import AccountEditCom from "../components/account_edit/AccountEditCom";
export default function AccountEditPage() {
return (
<AccountEditCom />
)
}
+8
View File
@@ -0,0 +1,8 @@
import React from 'react'
import FileUploadCom from "../components/file_uploads/FileUploadCom";
export default function FilesUploadPage() {
return (
<FileUploadCom />
)
}
+8
View File
@@ -0,0 +1,8 @@
import React from 'react'
import RecentAccounts from "../components/recent_accounts/RecentAccounts";
export default function RecentAccountsPage() {
return (
<RecentAccounts />
)
}
+2
View File
@@ -16,10 +16,12 @@ const queryKeys = {
billings: ['billings'],
right_sidebar: ['right_sidebar'],
recent_signup: ['recent_signup'],
recent_account: ['recent_account'],
products: ['products'],
products_template: ['products_template'],
custom_template: ['custom_template'],
account_view: ['account_view'],
account_edit: ['account_edit'],
subscriptions_view: ['subscriptions_view'],
users_admin: ['users_admin'],
country_list: ['country_list'],
+19
View File
@@ -88,6 +88,12 @@ export const getBillings = (reqData) => {
return getAuxEnd(`/billings`, postData)
}
// FUNCTION TO GET FILES
export const getFilesUpload = (reqData) => {
const postData = { ...reqData }
return getAuxEnd(`/file-uploads`, postData)
}
// FUNCTION TO GET SUBSCRIPTIONS
export const getSubscriptions = (reqData) => {
const postData = { ...reqData }
@@ -106,6 +112,11 @@ export const getRecentSignup = (reqData) => {
return getAuxEnd(`/recent-signup`, postData)
}
export const getRecentAccount = (reqData) => {
const postData = { ...reqData }
return getAuxEnd(`/recent-account`, postData)
}
// FUNCTION TO GET PRODUCTS DATA
export const getProducts = (reqData) => {
const postData = { ...reqData }
@@ -118,6 +129,14 @@ export const getUsers = (reqData) => {
return getAuxEnd(`/users`, postData)
}
// FUNCTION TO UPDATE USER PROFILE
export const updateUserProfile = (reqData) => {
let postData = {
...reqData
}
return postAuxEnd('/office/update-profile', postData, false)
}
// FUNCTION TO GET PRODUCTS TEMPLATE DATA
export const getProductsTemplate = (reqData) => {
const postData = { ...reqData }