Compare commits

..

81 Commits

Author SHA1 Message Date
victorAnumudu ef24f269dc refresh provisioning bug fixed 2026-05-20 18:14:00 +01:00
ameye 51df8293c3 Merge branch 'socket-provision-refresh' of MERMS/MermsPanelReactJS into master 2026-05-19 21:52:35 +00:00
victorAnumudu 546b6177dc made product page to reload on when provision is done 2026-05-19 18:20:10 +01:00
ameye db116f36e3 Merge branch 'img-list-width-adjust' of MERMS/MermsPanelReactJS into master 2026-05-18 17:27:24 +00:00
victorAnumudu 4f93c5b2c2 img list width adjusted 2026-05-18 18:25:49 +01:00
ameye 0a3ca0234e Merge branch 'captcha-centering' of MERMS/MermsPanelReactJS into master 2026-05-18 17:05:50 +00:00
victorAnumudu a88da5cf0e centralized captcha section 2026-05-18 17:12:00 +01:00
CHIEFSOFT\ameye 159f1fcdc8 Added report starter 2026-05-16 17:31:48 -04:00
CHIEFSOFT\ameye f50df99417 Email is required 2026-05-16 04:53:23 -04:00
CHIEFSOFT\ameye 9217e88831 Turnstile 2026-05-16 04:26:50 -04:00
CHIEFSOFT\ameye 49c6c6afe9 turnstile 2026-05-16 03:59:39 -04:00
CHIEFSOFT\ameye ffd00155e0 REACT_APP_TURNSTILE_SITE_KEY 2026-05-16 03:23:36 -04:00
CHIEFSOFT\ameye 9fd3636ffd NEXT_SITE_SECURITY_KEY 2026-05-16 03:14:41 -04:00
CHIEFSOFT\ameye 5ff6b4a4d2 NEXT_SITE_SECURITY_KEY 2026-05-16 03:08:05 -04:00
CHIEFSOFT\ameye 22c431c8e0 Turnstile 2026-05-16 02:47:51 -04:00
ameye 2db3a4d6f4 Merge branch 'picture-payload-removal' of MERMS/MermsPanelReactJS into master 2026-05-14 17:11:06 +00:00
victorAnumudu 1310561e55 removed picture payload from the update profile endpoint 2026-05-14 18:00:04 +01:00
ameye edcb6cac7d Merge branch 'login-bug-fix' of MERMS/MermsPanelReactJS into master 2026-05-11 17:13:11 +00:00
victorAnumudu be8460c20b login bug fixed 2026-05-11 18:08:43 +01:00
ameye 0794dfb8e0 templAte fix 2026-05-03 13:55:52 -04:00
ameye d950988e62 Clean code 2026-04-26 14:22:19 -04:00
ameye 295473d416 Commnented Image 2026-04-26 14:19:47 -04:00
ameye acc2c6eab4 comment fixed node version 2026-04-26 04:50:01 -04:00
ameye 9f6db5d13d removed yarn 2026-04-26 03:42:26 -04:00
ameye 71e66d3783 FROM alpine:3.22 2026-04-26 03:32:14 -04:00
ameye 2c7085b04b Fix messages 2026-04-26 03:14:54 -04:00
ameye 2c7584c00f Merge branch 'signup-endpoint-fix' of MERMS/MermsPanelReactJS into master 2026-04-22 21:35:50 +00:00
victorAnumudu 4d00178d94 signup endpoint bug fixed 2026-04-22 18:19:54 +01:00
tokslaw 863da32f93 Merge branch 'link-with-https' of MERMS/MermsPanelReactJS into master 2026-03-21 16:53:52 +00:00
tokslaw b9ad9e922e link added to terms conditions and privacy 2026-03-21 12:42:37 -04:00
victorAnumudu 5ad75e412b https validator added 2026-02-25 19:49:31 +01:00
ameye e9d6689418 Merge branch 'link-update-validation-removed' of MERMS/MermsPanelReactJS into master 2026-02-25 07:46:25 +00:00
victorAnumudu ed38e0db98 made update link params optional 2026-02-24 09:18:06 +01:00
ameye 351f027a6f Merge branch 'payment-table-update' of MERMS/MermsPanelReactJS into master 2026-02-23 22:50:07 +00:00
victorAnumudu 20d56ee307 payment table updated 2026-02-23 19:14:36 +01:00
ameye f5c798c6c1 Merge branch 'header-profile-image' of MERMS/MermsPanelReactJS into master 2026-02-16 11:27:02 +00:00
victorAnumudu a11f5100ba header profile image bug fixed 2026-02-16 11:32:16 +01:00
CHIEFSOFT\ameye 7628a561f8 Merge branch 'master' of https://gitlab.chiefsoft.net/MERMS/MermsPanelReactJS 2026-02-15 17:33:56 -05:00
CHIEFSOFT\ameye 615acff0b7 fixed reports 2026-02-15 17:33:34 -05:00
ameye aea161ccaf Merge branch 'profile-picture-upload' of MERMS/MermsPanelReactJS into master 2026-02-14 11:00:42 +00:00
victorAnumudu 66a2bcacd5 profile image upload added 2026-02-10 18:17:44 +01:00
ameye ec9d84b779 Merge branch 'truncating-text' of MERMS/MermsPanelReactJS into master 2026-02-10 17:00:38 +00:00
victorAnumudu 8ee793d11e text truncated 2026-02-09 19:11:17 +01:00
ameye 46f17cbdfe Merge branch 'template-payload-fix' of MERMS/MermsPanelReactJS into master 2026-01-21 09:45:48 +00:00
victorAnumudu 283994e43a pauload fixed 2026-01-21 10:25:56 +01:00
ameye b7d242ff0a Merge branch 'template-media-set' of MERMS/MermsPanelReactJS into master 2026-01-21 08:44:57 +00:00
victorAnumudu f6bae30bdd btn name changed 2026-01-21 04:19:05 +01:00
victorAnumudu a7e2e865de added template media set endpoint 2026-01-21 03:27:48 +01:00
ameye 51887cf0d6 Merge branch 'upload-btn-bug' of MERMS/MermsPanelReactJS into master 2026-01-20 17:11:43 +00:00
victorAnumudu 5ea1047356 image pop up added 2026-01-20 17:15:58 +01:00
victorAnumudu 9b9a7cc5da fixed upload btn display 2026-01-20 14:31:55 +01:00
ameye 290e1fbd7e Merge branch 'image-list-resized' of MERMS/MermsPanelReactJS into master 2026-01-19 20:44:45 +00:00
victorAnumudu 6884aa19b2 fixed image size 2026-01-19 19:36:21 +01:00
CHIEFSOFT\ameye 597a45dcba site image 2026-01-18 18:13:58 -05:00
CHIEFSOFT\ameye 4f7274c30c load template settings 2026-01-17 19:05:12 -05:00
ameye 290356780c Merge branch 'config-temp' of MERMS/MermsPanelReactJS into master 2026-01-13 08:14:09 +00:00
victorAnumudu bd1450887b added config template page 2026-01-13 05:01:23 +01:00
CHIEFSOFT\ameye 923a2483ed Added report starter 2026-01-12 12:08:13 -05:00
ameye 7f0ccf35b2 Merge branch 'report-endpoints' of MERMS/MermsPanelReactJS into master 2026-01-09 19:09:59 +00:00
victorAnumudu af2fdcede6 added endpoint for payment, product and system reports 2026-01-09 18:10:34 +01:00
ameye a9f2136125 Merge branch 'system-table' of MERMS/MermsPanelReactJS into master 2026-01-09 12:24:14 +00:00
victorAnumudu 5d04abc0db added system table report 2026-01-09 09:01:22 +01:00
CHIEFSOFT\ameye 26d095afda System table added 2026-01-08 11:52:24 -05:00
CHIEFSOFT\ameye cd030d8d12 Merge branch 'master' of https://gitlab.chiefsoft.net/MERMS/MermsPanelReactJS 2026-01-07 04:02:14 -05:00
ameye 4a0b7925d0 Merge branch 'report-tab' of MERMS/MermsPanelReactJS into master 2026-01-07 09:01:59 +00:00
victorAnumudu 9d947f19b9 fixed report tab bug 2026-01-06 20:27:07 +01:00
CHIEFSOFT\ameye 7a99beab57 Merge branch 'master' of https://gitlab.chiefsoft.net/MERMS/MermsPanelReactJS 2026-01-05 15:08:17 -05:00
ameye 02731c021a Merge branch 'file-list-refresh' of MERMS/MermsPanelReactJS into master 2026-01-05 20:07:37 +00:00
victorAnumudu fecae9d626 file list refresh and scroll bar added 2026-01-05 14:37:19 +01:00
CHIEFSOFT\ameye a4867a1b73 Added report starter 2026-01-03 12:09:35 -05:00
CHIEFSOFT\ameye 51c2e4b568 Added report starter 2026-01-03 12:05:42 -05:00
CHIEFSOFT\ameye 756c084059 Added report starter 2026-01-03 11:04:15 -05:00
CHIEFSOFT\ameye 064973e190 Added report starter 2026-01-03 10:24:34 -05:00
CHIEFSOFT\ameye d165e9dc0f upload page 2026-01-01 12:19:36 -05:00
CHIEFSOFT\ameye 77109e8369 Added Media starter icons 2026-01-01 11:23:16 -05:00
CHIEFSOFT\ameye 2924401c9b media files 2026-01-01 01:05:48 -05:00
CHIEFSOFT\ameye f6c2b1129d server path 2025-12-28 14:34:21 -05:00
CHIEFSOFT\ameye 8ba167a17c panel values 2025-12-28 14:13:16 -05:00
CHIEFSOFT\ameye 7f65b286b1 Media upload 2025-12-28 10:58:56 -05:00
CHIEFSOFT\ameye afc9e5be0f Added Media starter 2025-12-14 14:26:11 -05:00
ameye 7aefb555ed Merge branch 'user-picture' of MERMS/MermsPanelReactJS into master 2025-12-02 22:58:40 +00:00
46 changed files with 13826 additions and 6169 deletions
+8
View File
@@ -19,3 +19,11 @@ REACT_APP_TIMEOUT=600000
# show download button
REACT_APP_SHOW_DOWNLOAD=0
REACT_APP_DNS1='dns1.mermsemr.net'
REACT_APP_DNS2='dns1.mermsemr.net'
REACT_APP_DNS_LINK='https://qa-www.mermsemr.com/info/dns'
#CLOUDFLARE
REACT_APP_TURNSTILE_SITE_KEY=0x4AAAAAADQV82wuocFR-u5O
+7
View File
@@ -20,3 +20,10 @@ REACT_APP_TIMEOUT=600000
# show download button
REACT_APP_SHOW_DOWNLOAD=0
REACT_APP_DNS1='dns1.mermsemr.net'
REACT_APP_DNS2='dns1.mermsemr.net'
REACT_APP_DNS_LINK='https://qa-www.mermsemr.com/info/dns'
#CLOUDFLARE
REACT_APP_TURNSTILE_SITE_KEY=0x4AAAAAADQV82wuocFR-u5O
+9 -1
View File
@@ -17,4 +17,12 @@ REACT_APP_TERMS_LINK='https://www.mermsemr.com/terms'
REACT_APP_TIMEOUT=600000
# show download button
REACT_APP_SHOW_DOWNLOAD=0
REACT_APP_SHOW_DOWNLOAD=0
REACT_APP_DNS1='dns1.mermsemr.net'
REACT_APP_DNS2='dns1.mermsemr.net'
REACT_APP_DNS_LINK='https://www.mermsemr.com/info/dns'
#CLOUDFLARE
REACT_APP_TURNSTILE_SITE_KEY=0x4AAAAAADQV82wuocFR-u5O
+8
View File
@@ -15,3 +15,11 @@ REACT_APP_TERMS_LINK='https://qa-www.mermsemr.com/terms'
# Inactivity timeout/logout AT 10MINS
REACT_APP_TIMEOUT=600000
REACT_APP_DNS1='dns1.mermsemr.net'
REACT_APP_DNS2='dns1.mermsemr.net'
REACT_APP_DNS_LINK='https://qa-www.mermsemr.com/info/dns'
#CLOUDFLARE
REACT_APP_TURNSTILE_SITE_KEY=0x4AAAAAADQV82wuocFR-u5O
+3 -3
View File
@@ -1,12 +1,12 @@
version: '3'
services:
merms-panel:
image: registry.chiefsoft.net/merms-panel-reactjs:latest
# image: registry.chiefsoft.net/merms-panel-reactjs:latest
build:
context: .
dockerfile: docker/Dockerfile
args:
- NODE_ENV=development
# args:
# - NODE_ENV=development
restart: unless-stopped
ports:
- 8090:3000
+22 -22
View File
@@ -1,12 +1,12 @@
# pull the base image
# FROM node:alpine
FROM alpine:3.15
FROM alpine:3.22
# Build args
ARG NODE_ENV
ENV NODE_VERSION 14.19.0
ENV NODE_VERSION=14.19.0
ENV NODE_ENV=$NODE_ENV
# install nginx
@@ -82,26 +82,26 @@ RUN addgroup -g 1000 node \
&& node --version \
&& npm --version
ENV YARN_VERSION 1.22.17
# ENV YARN_VERSION 1.22.17
RUN apk add --no-cache --virtual .build-deps-yarn curl gnupg tar \
&& for key in \
6A010C5166006599AA17F08146C2130DFD2497F5 \
; do \
gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys "$key" || \
gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "$key" ; \
done \
&& curl -fsSLO --compressed "https://yarnpkg.com/downloads/$YARN_VERSION/yarn-v$YARN_VERSION.tar.gz" \
&& curl -fsSLO --compressed "https://yarnpkg.com/downloads/$YARN_VERSION/yarn-v$YARN_VERSION.tar.gz.asc" \
&& gpg --batch --verify yarn-v$YARN_VERSION.tar.gz.asc yarn-v$YARN_VERSION.tar.gz \
&& mkdir -p /opt \
&& tar -xzf yarn-v$YARN_VERSION.tar.gz -C /opt/ \
&& ln -s /opt/yarn-v$YARN_VERSION/bin/yarn /usr/local/bin/yarn \
&& ln -s /opt/yarn-v$YARN_VERSION/bin/yarnpkg /usr/local/bin/yarnpkg \
&& rm yarn-v$YARN_VERSION.tar.gz.asc yarn-v$YARN_VERSION.tar.gz \
&& apk del .build-deps-yarn \
# smoke test
&& yarn --version
# RUN apk add --no-cache --virtual .build-deps-yarn curl gnupg tar \
# && for key in \
# 6A010C5166006599AA17F08146C2130DFD2497F5 \
# ; do \
# gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys "$key" || \
# gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "$key" ; \
# done \
# && curl -fsSLO --compressed "https://yarnpkg.com/downloads/$YARN_VERSION/yarn-v$YARN_VERSION.tar.gz" \
# && curl -fsSLO --compressed "https://yarnpkg.com/downloads/$YARN_VERSION/yarn-v$YARN_VERSION.tar.gz.asc" \
# && gpg --batch --verify yarn-v$YARN_VERSION.tar.gz.asc yarn-v$YARN_VERSION.tar.gz \
# && mkdir -p /opt \
# && tar -xzf yarn-v$YARN_VERSION.tar.gz -C /opt/ \
# && ln -s /opt/yarn-v$YARN_VERSION/bin/yarn /usr/local/bin/yarn \
# && ln -s /opt/yarn-v$YARN_VERSION/bin/yarnpkg /usr/local/bin/yarnpkg \
# && rm yarn-v$YARN_VERSION.tar.gz.asc yarn-v$YARN_VERSION.tar.gz \
# && apk del .build-deps-yarn \
# # smoke test
# && yarn --version
# set working directory
# WORKDIR /app
@@ -109,7 +109,7 @@ RUN apk add --no-cache --virtual .build-deps-yarn curl gnupg tar \
# add `/app/node_modules/.bin` to $PATH
# ENV PATH /app/node_modules/.bin:$PATH
ENV PATH /usr/src/app/node_modules/.bin:$PATH
ENV PATH=/usr/src/app/node_modules/.bin:$PATH
COPY nginx.conf ./
+12137 -5671
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -8,6 +8,7 @@
"@fullcalendar/interaction": "^6.1.15",
"@fullcalendar/react": "^6.1.15",
"@fullcalendar/timegrid": "^6.1.15",
"@marsidev/react-turnstile": "^1.5.2",
"@popperjs/core": "^2.11.8",
"@reduxjs/toolkit": "^2.4.0",
"@stripe/react-stripe-js": "^3.9.1",
+7
View File
@@ -51,4 +51,11 @@ button{
.accordion-button, .accordion-button:not(.collapsed) {
background-color: transparent!important;
}
@media (min-width: 992px) {
.modal-50 {
min-width: 50%;
max-width: 50%;
}
}
+2
View File
@@ -26,6 +26,7 @@ import ProfileCompletePage from './views/ProfileCompletePage';
import SubscribePage from './views/Subscribe'
import StartPage from "./views/StartPage";
import TrafficPage from "./views/TrafficPage";
import MyMediaPage from './views/MyMediaPage.jsx';
function AppRouters() {
return (
@@ -52,6 +53,7 @@ function AppRouters() {
<Route path={siteLinks.profile_complete} element={<ProfileCompletePage/>}/>
<Route path={siteLinks.product} element={<ProductPage/>}/>
<Route path={siteLinks.reports} element={<ReportsPage/>}/>
<Route path={siteLinks.my_media} element={<MyMediaPage />}/>
<Route path={siteLinks.comments} element={<CommentsPage/>}/>
<Route path={siteLinks.contacts} element={<ContactsPage/>}/>
<Route path={siteLinks.user} element={<UserPage/>}/>
Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

+1 -1
View File
@@ -56,7 +56,7 @@ export default function CSignup() {
},
onSuccess: (res) => {
if(res?.data?.resultCode != '0'){
throw({message: res?.data?.resultDescription})
throw({message: res?.data?.error_message})
}
const {token, room, uid} = res?.data
if(!token || !room){
+17 -5
View File
@@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react'
import { Form, Formik } from "formik";
import { Turnstile } from '@marsidev/react-turnstile'
import * as Yup from "yup";
// import LoginImg from '../../assets/bg/login.svg'
@@ -17,13 +18,14 @@ const validationSchema = Yup.object().shape({
// /^[^0-9][a-zA-Z0-9._%+-]+@[a-zA-Z]+(\.[a-zA-Z]+)+$/,
// "Invalid email format"
// )
.min(3, "Minimum 3 characters")
.max(25, "Maximum 25 characters")
.required("Email is required"),
.min(3, "Username must be at least 3 characters")
.max(25, "Entered Username is too long")
.required("Enter a valid username to continue"),
})
const initialValues = {
username: ''
username: '',
turnstileToken: ''
};
export default function Forgetpwd2() {
@@ -77,8 +79,18 @@ export default function Forgetpwd2() {
</div>
</>
}
<div className="text-center col-12 mt-3">
<Turnstile
siteKey={process.env.REACT_APP_TURNSTILE_SITE_KEY}
onSuccess={(token) => props.setFieldValue('turnstileToken', token)}
onExpire={() => props.setFieldValue('turnstileToken', null)}
onError={() => props.setFieldValue('turnstileToken', null)}
/>
</div>
<div className="col-12 mt-3 text-end">
<button type='submit' className="btn btn-primary text-uppercase">{mutation.isPending ? 'loading...' : 'Send'}</button>
<button type='submit' disabled={!props.values.turnstileToken || mutation.isPending} className="btn btn-primary text-uppercase">{mutation.isPending ? 'loading...' : 'Send'}</button>
</div>
</>
:
+18 -7
View File
@@ -1,6 +1,7 @@
import React, { useEffect, useState } from 'react'
import { useMutation } from '@tanstack/react-query'
import { useDispatch, useSelector } from 'react-redux'
import { Turnstile } from '@marsidev/react-turnstile'
// import LoginImg from '../../assets/bg/login.svg'
@@ -26,6 +27,8 @@ export default function Login() {
remember: false
})
const [turnstileToken, setTurnstileToken] = useState(null)
const handleChange = ({target:{name, value}}) => {
if(name == 'remember'){
return setFields(prev => ({...prev, remember:!prev.remember}))
@@ -34,13 +37,13 @@ export default function Login() {
}
const login = useMutation({
mutationFn: (fields) => {
mutationFn: ({ turnstileToken, ...fields }) => {
if(!fields.username || !fields.password){
throw new Error('Please provide all fields marked *')
}
rememberMe(fields.remember) // FUNCTION TO SAVE USERNAME OF THE USER TO LOCAL STORAGE
delete fields.remember // REMOVING REMEMBER FROM THE PAYLOAD
return loginUser(fields)
rememberMe(fields.remember)
delete fields.remember
return loginUser({ ...fields, turnstileToken })
},
onError: (error) => {
console.log(error)
@@ -69,7 +72,7 @@ export default function Login() {
}
}
useEffect(()=>{ // NAVIGATES USER TO HOME PAGE IF USER IS ACTIVE
useEffect(()=>{ // NAVIGATES USER TO HOME PAGE IF USER IS CURRENTLY ACTIVE
if(loggedIn){
navigate(siteLinks.dash)
}
@@ -101,6 +104,14 @@ export default function Login() {
<input maxLength={25} name='password' value={fields.password} onChange={handleChange} type="password" className="form-control" placeholder="Password" />
</div>
</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="col-12">
<div className="d-block d-sm-flex align-items-center">
<div className="form-check">
@@ -120,12 +131,12 @@ export default function Login() {
</>
}
<div className="col-12 mt-3 text-end">
<button type='button' onClick={()=>{login.mutate(fields)}} className="btn btn-primary text-uppercase">{login.isPending ? 'loading...' : 'Sign In'}</button>
<button type='button' onClick={()=>{login.mutate({...fields, turnstileToken})}} disabled={!turnstileToken || login.isPending} className="btn btn-primary text-uppercase">{login.isPending ? 'loading...' : 'Sign In'}</button>
</div>
<div className="col-12 mt-3">
<p> <Link to={siteLinks.signup}>
{/*<span style={{fontWeight: 'bolder'}}>Sign Up</span>*/}
<button className="btn btn-warning text-uppercase">
<button type='button' className="btn btn-warning text-uppercase">
Sign Up
</button>
</Link><span style={{paddingLeft: '5px' , fontWeight: 'bolder'}}> if you don't have an account yet.</span></p>
+20 -2
View File
@@ -1,5 +1,6 @@
import React, {useState} from 'react'
import {Form, Formik} from "formik";
import { Turnstile } from '@marsidev/react-turnstile'
import * as Yup from "yup";
// import LoginImg from '../../assets/bg/login.svg'
@@ -33,6 +34,7 @@ const initialValues = {
firstname: '',
lastname: '',
isChecked: false,
turnstileToken: '',
// username: '',
// password: ''
};
@@ -135,7 +137,15 @@ export default function Signup2() {
onChange={props.handleChange}/>
<label className="form-check-label"
htmlFor="gridCheck">
I accept terms & policy
I accept{" "}
<a
href={process.env.REACT_APP_TERMS_LINK}
target="_blank"
rel="noopener noreferrer"
onClick={(e) => e.stopPropagation()}
>
terms & policy
</a>
</label>
</div>
<span
@@ -149,9 +159,17 @@ export default function Signup2() {
</div>
</>
}
<div className="text-center col-12 mt-3">
<Turnstile
siteKey={process.env.REACT_APP_TURNSTILE_SITE_KEY}
onSuccess={(token) => props.setFieldValue('turnstileToken', token)}
onExpire={() => props.setFieldValue('turnstileToken', null)}
onError={() => props.setFieldValue('turnstileToken', null)}
/>
</div>
<div className="col-12 mt-3 text-end">
<button type='submit'
disabled={!props.values.turnstileToken || mutation.isPending}
className="btn btn-primary text-uppercase">{mutation.isPending ? 'loading...' : 'Sign up'}</button>
</div>
</>
+243 -219
View File
@@ -2,10 +2,10 @@
import React, { useEffect, useState } from "react";
import BreadcrumbComBS from "../breadcrumb/BreadcrumbComBS";
import getImage from "../../utils/getImage";
import { useMutation, useQuery } from "@tanstack/react-query";
import { useMutation } from "@tanstack/react-query";
import { commentsData } from "../../services/services";
import queryKeys from "../../services/queryKeys";
import getCustomTime from "../../utils/getCustomTime";
// import queryKeys from "../../services/queryKeys";
// import getCustomTime from "../../utils/getCustomTime";
export default function Comments() {
// const {data:contacts, isFetching, isError, error} = useQuery({
@@ -65,50 +65,50 @@ export default function Comments() {
const contactsCategory = getContactData?.data?.data?.category; // LIST OF CATEGORY
return (
<>
<BreadcrumbComBS title="Comments" paths={["Dashboard", "Comments"]} />
{getContactData?.isPending ? (
<>
<div className="row">
<div className="col-12">
<p className="text-mute">Loading...</p>
</div>
</div>
</>
) : getContactData?.error ? (
<div className="row">
<div className="col-12">
<p className="text-danger">{getContactData?.error?.message}</p>
</div>
<>
<BreadcrumbComBS title="Comments" paths={["Dashboard", "Comments"]} />
{getContactData?.isPending ? (
<>
<div className="row">
<div className="col-12">
<p className="text-mute">Loading...</p>
</div>
) : (
<div className="row">
{/*<div className="vh-100 col-12 flex align-items-center">Coming Soon</div>*/}
<div className="col-12">
<div
className="card card-statistics mail-contant"
style={{ minHeight: "200px", borderRadius: "10px" }}
>
<div className="card-body p-0">
<div className="row no-gutters">
<div className="col-md-4 col-xxl-2 col-md-4">
<div className="mail-sidebar">
<div className="row justify-content-center">
<div className="d-none col-12">
<div className="text-center mail-sidebar-title px-4">
<a
href="#"
className="btn btn-primary btn-block py-3 font-weight-bold font-18"
>
<i className="fa fa-plus pl-2"></i>
</a>
</div>
</div>
<div className="col-12">
<div className="px-4 py-4">
<ul className="pl-0">
<li className="py-2">
<a href="#">
</div>
</>
) : getContactData?.error ? (
<div className="row">
<div className="col-12">
<p className="text-danger">{getContactData?.error?.message}</p>
</div>
</div>
) : (
<div className="row">
{/*<div className="vh-100 col-12 flex align-items-center">Coming Soon</div>*/}
<div className="col-12">
<div
className="card card-statistics mail-contant"
style={{ minHeight: "200px", borderRadius: "10px" }}
>
<div className="card-body p-0">
<div className="row no-gutters">
<div className="col-md-4 col-xxl-2 col-md-4">
<div className="mail-sidebar">
<div className="row justify-content-center">
<div className="d-none col-12">
<div className="text-center mail-sidebar-title px-4">
<a
href="#"
className="btn btn-primary btn-block py-3 font-weight-bold font-18"
>
<i className="fa fa-plus pl-2"></i>
</a>
</div>
</div>
<div className="col-12">
<div className="px-4 py-4">
<ul className="pl-0">
<li className="py-2">
<a href="#">
<span className="nav align-items-center">
<span>
<i className="fa fa-envelope-o text-primary pr-4"></i>
@@ -122,84 +122,89 @@ export default function Comments() {
</span>
</span>
</span>
</a>
</li>
{/*<li className="py-2">*/}
{/* <a href="#">*/}
{/* <span*/}
{/* className="nav align-items-center">*/}
{/* <span>*/}
{/* <i className="fa fa-paper-plane-o pr-4"></i>*/}
{/* </span>*/}
{/* <span>*/}
{/* <span>Replied Mail</span>*/}
{/* </span>*/}
{/* </span>*/}
{/* </a>*/}
{/*</li>*/}
</ul>
</a>
</li>
{/*<li className="py-2">*/}
{/* <a href="#">*/}
{/* <span*/}
{/* className="nav align-items-center">*/}
{/* <span>*/}
{/* <i className="fa fa-paper-plane-o pr-4"></i>*/}
{/* </span>*/}
{/* <span>*/}
{/* <span>Replied Mail</span>*/}
{/* </span>*/}
{/* </span>*/}
{/* </a>*/}
{/*</li>*/}
</ul>
<ul className="pl-0 mt-5">
<li
className="py-2"
onClick={() => changeActiveCategoryUID("0")}
style={{ cursor: "pointer" }}
>
<div>
<ul className="pl-0 mt-5">
<li
className="py-2"
onClick={() => changeActiveCategoryUID("0")}
style={{ cursor: "pointer" }}
>
<div>
<span className="nav align-items-center">
<span>
<i
className={`fa fa-circle-o pr-4 ${
activeCategoryUID == "0"
? "text-primary"
: "text-warning"
}`}
className={`fa fa-circle-o pr-4 ${
activeCategoryUID == "0"
? "text-primary"
: "text-warning"
}`}
></i>
</span>
<span>
<span>All</span>
</span>
</span>
</div>
</li>
{contactsCategory &&
contactsCategory.map((item) => (
<li
key={item?.cid}
className="py-2"
onClick={() =>
changeActiveCategoryUID(`${item?.cid}`)
}
style={{ cursor: "pointer" }}
>
<div>
</div>
</li>
{contactsCategory &&
contactsCategory.map((item) => (
<li
key={item?.cid}
className="py-2"
onClick={() =>
changeActiveCategoryUID(`${item?.cid}`)
}
style={{ cursor: "pointer" }}
>
<div>
<span className="nav align-items-center">
<span>
<i
className={`fa fa-circle-o pr-4 ${
activeCategoryUID ==
`${item?.cid}`
? "text-primary"
: "text-warning"
}`}
className={`fa fa-circle-o pr-4 ${
activeCategoryUID ==
`${item?.cid}`
? "text-primary"
: "text-warning"
}`}
></i>
</span>
<span>
<span>{item?.description}</span>
</span>
</span>
</div>
</li>
))}
</ul>
</div>
</div>
</div>
</li>
))}
</ul>
</div>
</div>
</div>
<div className={`${filteredContactData.length > 0 ? 'col-md-8 col-xxl-4' : 'col-md-8 col-xxl-10'} border-md-t`}>
<div className="mail-content border-right border-n h-100" style={{placeContent: 'center'}}>
{/* <div className="mail-search border-bottom">
</div>
</div>
<div
className={`${filteredContactData.length > 0 ? "col-md-8 col-xxl-4" : "col-md-8 col-xxl-10"} border-md-t`}
>
<div
className="mail-content border-right border-n h-100"
style={{ placeContent: "center" }}
>
{/* <div className="mail-search border-bottom">
<div className="row align-items-center mx-0">
<div className="col-12">
<div className="form-group pt-3">
@@ -209,141 +214,160 @@ export default function Comments() {
</div>
</div>
</div> */}
<div className="mail-msg scrollbar scroll_dark">
{ filteredContactData.length ?
filteredContactData?.map((contact, index) => {
const isActive =
contact?.uid == activeContactUID ||
(!activeContactUID && index == 0);
const avtarImage =
contact?.category === undefined
? "avtar/01.jpg"
: "avtar/" + contact.category + ".png";
return (
<div
key={contact?.uid}
onClick={() => changeActiveUID(contact?.uid)}
className={`mail-msg-item ${
isActive && "bg-light"
}`}
>
<a href="#">
<div className="media align-items-center">
<div className="mr-3">
<div className="bg-img">
<img
src={getImage(avtarImage)}
className="img-fluid"
alt="user"
/>
</div>
</div>
<div className="w-100">
<div className="mail-msg-item-titel justify-content-between">
<p>
<div className="mail-msg scrollbar scroll_dark">
{filteredContactData.length ? (
filteredContactData?.map((contact, index) => {
const isActive =
contact?.uid == activeContactUID ||
(!activeContactUID && index == 0);
const avtarImage =
contact?.category === undefined
? "avtar/01.jpg"
: "avtar/" + contact.category + ".png";
return (
<div
key={contact?.uid}
onClick={() => changeActiveUID(contact?.uid)}
className={`mail-msg-item ${
isActive && "bg-light"
}`}
>
<a href="#">
<div className="media align-items-center">
<div className="mr-3">
<div className="bg-img">
<img
src={getImage(avtarImage)}
className="img-fluid"
alt="user"
/>
</div>
</div>
<div className="w-100">
<div className="mail-msg-item-titel justify-content-between">
<p>
<span
style={{
fontSize: "14px",
color: "#148399",
fontWeight: "bolder",
}}
style={{
fontSize: "14px",
color: "#148399",
fontWeight: "bolder",
}}
>
{contact?.sender}
</span>
</p>
{/* <p className="d-none d-xl-block">06:59 <span> PM </span></p> */}
<p className="d-none d-xl-block">
</p>
{/* <p className="d-none d-xl-block">06:59 <span> PM </span></p> */}
<p className="d-none d-xl-block">
<span style={{ fontSize: "14px" }}>
{new Date(
contact?.added
contact?.added,
).toDateString()}
</span>
</p>
</div>
<h5 className="mb-0 my-2">
{contact?.title}
</h5>
<p>
{contact?.message?.length < 100
? contact?.message
: contact?.message.substring(0, 101) +
" ..."}
</p>
<p className="d-xl-none">
</p>
</div>
<h5 className="mb-0 my-2">
{contact?.title}
</h5>
<p>
{contact?.message?.length < 100
? contact?.message
: contact?.message.substring(0, 101) +
" ..."}
</p>
<p className="d-xl-none">
<span>
{new Date(
contact?.added
contact?.added,
).toDateString()}
{/* {getCustomTime(contact.added)} */}
</span>
</p>
</div>
</div>
</a>
</div>
);
})
:
<p className="text-center">Messages will appear here as soon as they are available for selection</p>
}
</div>
</div>
</p>
</div>
</div>
</a>
</div>
);
})
) : (
<p className="text-center">
Messages will appear here as soon as they are
available for selection
</p>
)}
</div>
{filteredContactData.length > 0 &&
<div className="col-xxl-6 border-t border-xxl-t">
<div className="mail-chat py-5 px-5">
<div className="media align-items-center">
<div className="bg-img mr-3">
<img
src={activeContactUID ? getImage("avtar/" + activeDetail[0].category + ".png") : getImage(filteredContactData[0] == undefined ? "avtar/01.jpg": "avtar/" + filteredContactData[0].category + ".png")}
className="img-fluid"
alt="user"
/>
</div>
<div>
<h4 className="mb-0" style={{ color: "#148399" }}>
{activeContactUID
? activeDetail[0]?.sender
: filteredContactData[0]?.sender}
</h4>
<p>
{activeContactUID
? new Date(activeDetail[0]?.added).toDateString()
: new Date(
filteredContactData[0]?.added
).toDateString()}
</p>
</div>
</div>
<div className="mt-4 d-flex justify-content-between">
<div>
<h3>
{activeContactUID
? activeDetail[0]?.title
: filteredContactData[0]?.title}
</h3>
</div>
<div className="d-flex">
{/*<a href="javascript:void(0)"><i className="fa fa-reply font-22 pr-3"></i></a>*/}
{/*<a href="javascript:void(0)"><i className="fa fa-print font-22"></i></a>*/}
</div>
</div>
<div>
<p>
{activeContactUID
? activeDetail[0]?.message
: filteredContactData[0]?.message}
</p>
</div>
</div>
</div>
}
</div>
</div>
{filteredContactData.length > 0 && (
<div className="col-xxl-6 border-t border-xxl-t">
<div className="mail-chat py-5 px-5">
<div className="media align-items-center">
<div className="bg-img mr-3">
<img
src={
activeContactUID
? getImage(
"avtar/" +
activeDetail[0].category +
".png",
)
: getImage(
filteredContactData[0] == undefined
? "avtar/01.jpg"
: "avtar/" +
filteredContactData[0].category +
".png",
)
}
className="img-fluid"
alt="user"
/>
</div>
<div>
<h4 className="mb-0" style={{ color: "#148399" }}>
{activeContactUID
? activeDetail[0]?.sender
: filteredContactData[0]?.sender}
</h4>
<p>
{activeContactUID
? new Date(
activeDetail[0]?.added,
).toDateString()
: new Date(
filteredContactData[0]?.added,
).toDateString()}
</p>
</div>
</div>
<div className="mt-4 d-flex justify-content-between">
<div>
<h3>
{activeContactUID
? activeDetail[0]?.title
: filteredContactData[0]?.title}
</h3>
</div>
<div className="d-flex">
{/*<a href="javascript:void(0)"><i className="fa fa-reply font-22 pr-3"></i></a>*/}
{/*<a href="javascript:void(0)"><i className="fa fa-print font-22"></i></a>*/}
</div>
</div>
<div>
<p>
{activeContactUID
? activeDetail[0]?.message
: filteredContactData[0]?.message}
</p>
</div>
</div>
</div>
)}
</div>
</div>
</div>
)}
</>
</div>
</div>
)}
</>
);
}
+26 -10
View File
@@ -42,18 +42,34 @@ export default function SocketIOContextProvider({children}) {
// setSocketMsgReceived(data.message);
// dispatch(tableReload({type:'CHATMESSAGELIST'})) // dispatches to update chat message sending from owner to worker and vice versa
console.log('SOCKET RECEIVED DATA *** ', data)
queryClient.refetchQueries({
queryKey: [...queryKeys.recentAction],
// type: 'active',
// exact: true,
})
if(data?.message_action === socketOnEvents.refresh_all_actions){
queryClient.refetchQueries({
queryKey: [...queryKeys.recentAction],
// type: 'active',
// exact: true,
})
}
if(data?.message_action === socketOnEvents.refresh_provision){
queryClient.refetchQueries({ // refetches product Page API call
queryKey: [...queryKeys.product_page],
})
queryClient.refetchQueries({ // refetches productProvision API call
queryKey: [...queryKeys.myproduct_provision],
})
}
});
socket.on(socketOnEvents.refresh_provision, (data) => {
queryClient.refetchQueries({ // refetches productProvision API call
queryKey: [...queryKeys.myproduct_provision],
})
});
// socket.on(socketOnEvents.refresh_provision, (data) => {
// queryClient.refetchQueries({ // refetches productProvision API call
// queryKey: [...queryKeys.myproduct_provision],
// })
// // queryClient.invalidateQueries({ queryKey: [...queryKeys.product_page] })
// queryClient.refetchQueries({ // refetches product Page API call
// queryKey: [...queryKeys.product_page],
// })
// console.log('SOCKET RECEIVED DATA *** 111 ', data)
// });
// client-side
socket.on("connect", () => {
+1
View File
@@ -6,5 +6,6 @@ export const socketEmitEvents = {
export const socketOnEvents = {
receive_message: 'receive_message',
refresh_all_actions: 'refresh_all_actions',
refresh_provision: 'refresh_provision_actions'
}
+20 -15
View File
@@ -41,22 +41,27 @@ export default function Products() {
</div>
:
<div className="row m-b-20">
{products && products.map((product, index) => (
<div key={product.uid+index} className={`col-xxs-6 col-xl-4 col-xxl-6 mb-2 mb-xxl-0`}>
<Link to={productPath(product?.product_id)} >
<div className={`d-flex align-items-center extraProductCard ${product?.icon_style}`} style={{borderColor:'black', borderWidth: '2px'}} >
<div className="icon-container img-icon m-r-20 bg-light-gray rounded">
<i className={`fa ${product?.product_icon} text-primary`}></i>
{products && products.map((product, index) =>
{
// let productName = product?.name?.length > 14 ? product?.name?.substring(0, 14) + '...' : product?.name
return (
<div key={product.uid+index} className={`col-12 col-lg-6 mb-2 mb-xxl-0`}>
<Link to={productPath(product?.product_id)} >
<div className={`d-flex align-items-center extraProductCard ${product?.icon_style}`} style={{borderColor:'black', borderWidth: '2px'}} >
<div className="icon-container img-icon m-r-20 bg-light-gray rounded">
<i className={`fa ${product?.product_icon} text-primary`}></i>
</div>
<div className="report-details overflow-hidden">
<p><span style={{fontWeight: 'bolder', color: '#00557A'}}>{product?.status_text}</span></p>
<h4><span className='w-100 d-inline-block text-truncate' style={{paddingLeft: '10px'}}>{product?.name}</span></h4>
</div>
</div>
<div className="report-details">
<p><span style={{fontWeight: 'bolder', color: '#00557A'}}>{product?.status_text}</span></p>
<h4><span className='text-truncate' style={{paddingLeft: '10px'}}>{product?.name}</span></h4>
</div>
</div>
</Link>
</div>
))}
</Link>
</div>
)
}
)}
</div>
}
<div className="apexchart-wrapper">
@@ -11,7 +11,7 @@ export default function UserFooter(){
<p>&copy; Copyright {year}. All rights reserved.</p>
</div>
<div className="col col-sm-6 ml-sm-auto text-center text-sm-right">
<p>A division of <i className="fa fa-key text-danger mx-1"></i> autoMedSys A.I.</p>
<p>A division of MERMS(AI)</p>
</div>
</div>
</footer>
@@ -48,6 +48,7 @@ export default function UserMenu() {
</div>
</Link>
<ul id="collapseTwo" className="collapse" aria-labelledby="headingTwo" data-bs-parent="#sidebarNav">
<li className={`${pathname == siteLinks.my_media ? 'active' : ''}`}><Link to={siteLinks.my_media}>Files and Media</Link></li>
<li className={`${pathname == siteLinks.subscription ? 'active' : ''}`}><Link to={siteLinks.subscription}>Subscription</Link></li>
<li className={`${pathname == siteLinks.settings ? 'active' : ''}`}><Link to={siteLinks.settings}>Settings</Link></li>
</ul>
+248
View File
@@ -0,0 +1,248 @@
import React from "react";
import BreadcrumbComBS from "../breadcrumb/BreadcrumbComBS";
import {useState, useRef} from "react";
import {useQuery, useMutation, useQueryClient} from "@tanstack/react-query";
import queryKeys from "../../services/queryKeys";
import {getMediaFileList, uploadFile} from "../../services/services";
import getImage from "../../utils/getImage";
export default function MyMedia() {
const queryClient = useQueryClient()
const basePath = process.env.REACT_APP_MAIN_API
const [selectedFile, setSelectedFile] = useState(null);
const [message, setMessage] = useState('');
const [imageLink, setImageLink] = useState('')
console.log('imageLink', imageLink)
// Function to handle file selection
const handleFileChange = (event) => {
setSelectedFile(event.target.files[0]);
setMessage('');
};
// Function to handle the upload to the API
const _handleUpload = async () => {
if (!selectedFile) {
setMessage('Please select a file first!');
return;
}
const formData = new FormData();
// 'file' is the field name that your API endpoint expects
formData.append('file', selectedFile);
formData.append("member_uid", localStorage.getItem('uid'));
formData.append("token", localStorage.getItem('token'));
try {
// Replace with your actual API endpoint URL
const apiEndpoint = basePath + '/upload/webfiles';
const response = await fetch(apiEndpoint, {
method: 'POST',
// The browser automatically sets the 'Content-Type' header to
// 'multipart/form-data' when you provide a FormData object as the body,
// which is required for file uploads.
body: formData,
});
if (response.ok) {
const result = await response.json();
setMessage('File uploaded successfully!');
console.log('Success:', result);
} else {
setMessage('Upload failed.');
console.error('Upload failed:', response.statusText);
}
} catch (error) {
setMessage('An error occurred during the upload.');
console.error('Error:', error);
}
}
const uploadFileMutation = useMutation({
mutationFn: (fields) => {
if (!fields.file) {
throw({message: 'Please select a file first!'})
}
return uploadFile(fields)
},
onSuccess: (res) => {
// console.log('res', res.data)
// if(res.data.resultCode != '0' || !res?.data?.pending_uid){
// throw({message: res?.data?.resultDescription})
// }
setSelectedFile(null)
queryClient.refetchQueries({
queryKey: [...queryKeys.my_files],
// type: 'active',
// exact: true,
})
},
onSettled: () => {
setTimeout(() => {
uploadFileMutation.reset()
}, 3000)
}
})
const handleUpload = () => {
let reqData = {
token: localStorage.getItem("token"), // USER TOKEN
member_uid: localStorage.getItem("uid"), // USER UID
file: selectedFile
};
// console.log(reqData)
uploadFileMutation.mutate(reqData)
}
const {data, isFetching, isError, error} = useQuery({
queryKey: queryKeys.my_files,
queryFn: () => {
let reqData = {
token: localStorage.getItem('token'), // USER TOKEN
uid: localStorage.getItem('uid') // USER UID
}
return getMediaFileList(reqData)
}
})
const mediaFileList = data?.data
//debugger;
return (
<>
<BreadcrumbComBS title='Files' paths={['Dashboard', 'Files']}/>
<div className="row">
<div className="col-xl-6 col-xxl-4 m-b-30">
<div className="card card-statistics mb-0 widget-support-list">
<div className="card-header">
<div className="card-heading">
<h4 className="card-title">Upload File</h4>
</div>
</div>
<div className="card-body pl-0 pr-0 scrollbar scroll_dark">
<div className="widget-text">
<div>
<input className="form-control form-control-sm" type="file"
onChange={handleFileChange}/>
{selectedFile && (
<div>
<h4>Selected File Details:</h4>
<ul>
<li>Name: {selectedFile.name}</li>
<li>Type: {selectedFile.type}</li>
<li>Size: {selectedFile.size} bytes</li>
</ul>
<div style={{width: '100%', textAlign: 'right'}}>
<button
className="btn btn-square btn-inverse-light btn-xs d-inline-block mt-2 mb-0"
onClick={handleUpload}
disabled={!selectedFile || uploadFileMutation.isPending || uploadFileMutation.isSuccess}>
Upload
</button>
</div>
<>
{/* {message && <p>{message}</p>} */}
<p>{uploadFileMutation.isPending ? 'uploading...' : uploadFileMutation.isError ? uploadFileMutation?.error?.message : uploadFileMutation.isSuccess ? 'File Uploaded' : ''}</p>
</>
</div>
)}
</div>
</div>
</div>
</div>
</div>
<div className="col-xl-6 col-xxl-4 m-b-30">
<div className="card card-statistics h-100 mb-0 widget-downloads-list" style={{}}>
<div className="card-header d-flex justify-content-between">
<div className="card-heading">
<div className="card-heading">
<h4 className="card-title">Files List</h4>
</div>
</div>
</div>
{isFetching ?
<>
<div className="col-12">
<p className='text-mute'>Loading...</p>
</div>
</>
: isError ?
<div className="col-12">
<p className='text-danger'>{error?.message}</p>
</div>
:
<div className="card-body scrollbar scroll_dark"
style={{minHeight: '400px', maxHeight: '500px', overflowY: 'auto'}}>
{mediaFileList && mediaFileList?.file_list && mediaFileList?.file_list.map((item, index) => {
const file_url = (mediaFileList?.media_server + "/" + item?.file_group + "/" + item?.file_uid + "/" + item.filename).toLowerCase();
const avtarImage =
item?.file_type === undefined
? "icons/01.png"
: "icons/" + item.file_type + ".png";
return (<div key={index}
className={`widget-text ${imageLink == file_url && 'bg-light'}`}>
<div className={`media align-items-center`}
onClick={() => setImageLink(file_url)} style={{cursor: 'pointer'}}>
<img src={getImage(avtarImage)}
// src={`assets/img/file-icon/${item.file_type}.png`}
className="img-fluid"
alt={`${item.file_type}`}/>
<div className="media-body">
<h4 className="mb-0 ml-3">{item.filename}</h4>
</div>
<div>
<a href={`${file_url}`} target='_blank'
className="btn btn-icon btn-round btn-outline-success">
<i className="ti ti-download"></i>
</a>
<a href=""
className="btn btn-icon btn-round btn-outline-danger ml-2">
<i className="ti ti-close"></i>
</a>
</div>
</div>
</div>)
})
}
</div>
}
</div>
</div>
<div className="col-xl-6 col-xxl-4 m-b-30">
<div className="card card-statistics h-100 mb-0 widget-branches-list">
<div className="card-header">
<div className="card-heading">
<h4 className="card-title">Preview</h4>
</div>
</div>
<div
className="card-body d-flex justify-content-center align-items-center pl-0 pr-0 scrollbar scroll_dark">
{imageLink &&
<img className="w-100 h-auto" src={imageLink} alt='file-image'/>
}
</div>
<div className="card-header d-flex justify-content-between align-items-center">
{imageLink &&
<>
<p>0 x 0 px</p>
<p>size: 0 bytes</p>
</>
}
</div>
</div>
</div>
</div>
</>
)
}
+1 -1
View File
@@ -66,7 +66,7 @@ export default function ProductActive({productData}){
</button>
</div>
</div>
<div className="card-body" style={{minHeight: '680px'}}>
<div className="card-body" style={{minHeight: '600px', maxHeight: '600px'}}>
<iframe ref={iframe} style={{borderWidth: '0px'}} src={externalUrl} width="100%" height="600" title={externalUrl}></iframe>
</div>
<div className="p-4 ml-auto">
+26 -6
View File
@@ -5,6 +5,7 @@ import queryKeys from '../../../services/queryKeys';
import { useSelector } from 'react-redux';
import { useQuery } from '@tanstack/react-query';
import sortObjectByListOrder from '../../../helpers/sortObjectByListOrder'
import TemplateConfigure from './TemplateConfigure';
const Settings = memo(({productData}) => {
@@ -24,7 +25,7 @@ const Settings = memo(({productData}) => {
})
const settingsConfig = configData?.data?.settings_items
// console.log('CONFIG DATA...', settingsConfig)
// console.log('configData', configData?.data?.subscription_template)
const [fieldsChanged, setFieldsChanged] = useState(false)
@@ -81,12 +82,12 @@ const Settings = memo(({productData}) => {
</div>
</div>
:
<div className="tab tab-vertical">
<ul className="nav nav-tabs" role="tablist">
<div className="d-flex">
<ul className="bg-body-secondary flex-column nav" role="tablist" style={{width: '25%', minHeight: '670px', maxHeight: '670px'}}>
<>
{Object.entries(sortedSettingsConfig).map(([key, value], index) => (
<li key={key} className="nav-item">
<a className={`nav-link ${(activeTab == value.controls || (index == 0 & !activeTab)) && 'active show'}`}
<p className={`text-black nav-link ${(activeTab == value.controls || (index == 0 & !activeTab)) && 'active show bg-primary text-white'}`}
id={key}
// data-bs-toggle="pill"
// data-bs-target={`#${value.controls}`}
@@ -97,12 +98,27 @@ const Settings = memo(({productData}) => {
onClick={()=>handleChangeTab(value.controls)}
>
{value.title}
</a>
</p>
</li>
))}
{configData?.data?.subscription_template &&
<li className="mt-auto nav-item">
<p className={`text-black nav-link ${(activeTab == 'config_temp') && 'active show bg-primary text-white'}`}
// data-bs-toggle="pill"
// data-bs-target={`#${value.controls}`}
type="button"
// role="tab"
// aria-controls={value.controls}
// aria-selected="true"
onClick={()=>handleChangeTab('config_temp')}
>
Configure Template
</p>
</li>
}
</>
</ul>
<div className="tab-content">
<div className="p-3 tab-content" style={{width: '75%'}}>
<>
{Object.entries(sortedSettingsConfig).map(([key, value], index) => (
<div key={key} className={`tab-pane fade ${(activeTab == value.controls || (index == 0 & !activeTab)) && 'active show'}`}
@@ -112,6 +128,10 @@ const Settings = memo(({productData}) => {
<GeneralTab tabKey={key} name={value.title} data={value.data} isCustom={value.custom} productData={productData} backendValues={settingsData} setFieldsChanged={setFieldsChanged} />
</div>
))}
<div className={`tab-pane fade ${(activeTab == 'config_temp') && 'active show'}`}
>
<TemplateConfigure productData={productData} />
</div>
</>
</div>
</div>
@@ -0,0 +1,166 @@
import React, { useState } from "react";
//import getImage from "../../../utils/getImage";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import queryKeys from "../../../services/queryKeys";
import { getTemplateConfig } from "../../../services/services";
//import {Link} from "react-router-dom";
//import siteLinks from "../../../links/siteLinks";
import UploadModal from "./UploadModal";
const TemplateConfigure = ({ productData }) => {
const [selectedSectionDetails, setSelectedSectionDetails] = useState({});
const {
data: templateData,
isFetching,
isError,
error,
} = useQuery({
queryKey: queryKeys.myTemplateConfig,
queryFn: () => {
let reqData = {
token: localStorage.getItem("token"), // USER TOKEN
uid: localStorage.getItem("uid"), // USER UID
product_id: productData?.product_id,
};
return getTemplateConfig(reqData);
},
staleTime: 0,
});
const templateResponse = templateData?.data;
const templateImages = Array.isArray(templateResponse?.template_images?.data)
? templateResponse.template_images.data
: [];
console.log("templateResponse", templateResponse);
return (
<>
<div className="card card-statistics">
{isFetching ? (
<>
<div className="row">
<div className="col-12">
<p className="text-mute">Loading...</p>
</div>
</div>
</>
) : isError ? (
<div className="row">
<div className="col-12">
<p className="text-danger">{error?.message}</p>
</div>
</div>
) : (
<>
<div className="card-header">
<div className="card-heading">
<h4 className="card-title" style={{ textTransform: "none" }}>
{templateResponse?.template_name}
</h4>
</div>
</div>
<div>
<div className="col-12">
<div>
<div>
<div>
<h4>Image List</h4>
</div>
</div>
<div>
<ul className="list-unstyled">
{templateImages &&
templateImages.map((item) => {
const currImage = item?.default_val;
return (
<li className="media">
<div
style={{
display: "flex",
flexDirection: "row",
padding: "5px",
backgroundColor: "aliceblue",
margin: "2px",
maxHeight: "150px",
width: "100%",
}}
>
<div
className="d-flex justify-content-center align-items-center"
style={{
padding: "6px",
width: "120px",
height: "100px",
}}
>
<img
className="mb-xxs-0 img-fluid"
style={{
height: "auto",
maxHeight: "100px",
}}
src={currImage}
alt="image"
/>
</div>
<div
className="media-body"
style={{ padding: "2px" }}
>
<div
style={{
display: "flex",
flexDirection: "column",
}}
>
<div
style={{
textAlign: "right",
width: "100%",
}}
></div>
{/* [Change Image] */}
<label
onClick={() =>
setSelectedSectionDetails(item)
}
className="w-100 text-end"
data-bs-toggle="modal"
data-bs-target="#verticalCenter"
style={{ cursor: "pointer" }}
>
[Change Image]
</label>
{/* <input id={item?.id} name={item?.id} className="d-none form-control form-control-sm" type="file" onChange={handleFileChange}/> */}
<div>
<h5 className="mt-0 mb-1">
{item?.name}
</h5>
{item?.description}
</div>
</div>
</div>
</div>
</li>
);
})}
</ul>
</div>
</div>
</div>
</div>
</>
)}{" "}
</div>
<UploadModal
productId={productData?.product_id}
selectedSectionDetails={selectedSectionDetails}
/>
</>
);
};
export default TemplateConfigure;
@@ -1,141 +1,196 @@
import {Form, Formik} from "formik";
import { Form, Formik } from "formik";
import * as Yup from "yup";
import {useMutation} from '@tanstack/react-query';
import {setExternalURL} from '../../../services/services';
import { useMutation } from "@tanstack/react-query";
import { setExternalURL } from "../../../services/services";
import { useState } from "react";
import { Link } from "react-router-dom";
const validationSchema = Yup.object().shape({
url: Yup.string().required("URL is required").matches(/^https?:\/\/[a-zA-Z0-9\-]+\.[a-zA-Z0-9\-]+\.[a-zA-Z]+/, 'Must be like: https://example.mysite.com'),
})
url: Yup.string()
.required("URL is required")
.matches(
/^https?:\/\/[a-zA-Z0-9\-]+\.[a-zA-Z0-9\-]+\.[a-zA-Z]+/,
"Must be like: https://example.mysite.com",
),
});
// const initialValues = {
// url: '',
// };
const URLConfiguration = ({productData}) => {
const URLConfiguration = ({ productData }) => {
const [externalURLChanged, setExternalURLChanged] = useState(true);
const [externalURLChanged, setExternalURLChanged] = useState(true)
const initialValues = {
url: productData?.external_url || "",
};
const initialValues = {
url: productData?.external_url || '',
let defaultUrl = "https://" + productData?.internal_url;
let externalUrl = productData?.external_url;
const handleExternalURLChanged = (e) => {
if (e.target.value == externalUrl) {
setExternalURLChanged(true);
} else {
setExternalURLChanged(false);
}
};
// API to set url
const setURL = useMutation({
mutationFn: (fields) => {
return setExternalURL(fields);
},
onSuccess: (res) => {
if (res.data.resultCode != "0") {
// throw({message: res?.data?.resultDescription})
throw { message: "Something went wrong!" };
}
},
onSettled: () => {
setTimeout(() => {
setURL.reset();
}, 3000);
},
// onError: (err) => {
// console.log('err', err)
// }
});
const handleSubmit = (values) => {
let reqData = {
token: localStorage.getItem("token"), // USER TOKEN
uid: localStorage.getItem("uid"), // USER UID
subscription_uid: productData?.subscription_uid,
external_url: values.url,
};
setURL.mutate(reqData);
};
let defaultUrl = 'https://' + productData?.internal_url
let externalUrl = productData?.external_url
const handleExternalURLChanged = (e) => {
if(e.target.value == externalUrl){
setExternalURLChanged(true)
}else{
setExternalURLChanged(false)
}
}
// API to set url
const setURL = useMutation({
mutationFn: (fields) => {
return setExternalURL(fields)
},
onSuccess: (res) => {
if (res.data.resultCode != '0') {
// throw({message: res?.data?.resultDescription})
throw({message: 'Something went wrong!'})
}
},
onSettled: () => {
setTimeout(() => {
setURL.reset()
}, 3000)
}
// onError: (err) => {
// console.log('err', err)
// }
})
const handleSubmit = (values) => {
let reqData = {
token: localStorage.getItem('token'), // USER TOKEN
uid: localStorage.getItem('uid'), // USER UID
subscription_uid: productData?.subscription_uid,
external_url: values.url
}
setURL.mutate(reqData)
}
return <>
<div className="card card-statistics">
<div className="card-header">
<div className="card-heading">
<h4 className="card-title" style={{textTransform: 'none'}}>{defaultUrl}</h4>
</div>
</div>
{/*<div className="card-body">*/}
{/* <div className="form-group">*/}
{/* /!*<label htmlFor="exampleInputEmail1">Email address</label>*!/*/}
{/* <input type="email" className="form-control"*/}
{/* aria-describedby="defaultUrlHelp" value={defaultUrl} readOnly={true} />*/}
{/* </div>*/}
{/*</div>*/}
return (
<>
<div className="card card-statistics">
<div className="card-header">
<div className="card-heading">
<h4 className="card-title" style={{ textTransform: "none" }}>
{defaultUrl}
</h4>
</div>
</div>
{/*<div className="card-body">*/}
{/* <div className="form-group">*/}
{/* /!*<label htmlFor="exampleInputEmail1">Email address</label>*!/*/}
{/* <input type="email" className="form-control"*/}
{/* aria-describedby="defaultUrlHelp" value={defaultUrl} readOnly={true} />*/}
{/* </div>*/}
{/*</div>*/}
</div>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={handleSubmit}
>
{(props) => {
return (
<Form className='w-full'>
<div className="card card-statistics" style={{backgroundColor: '#b6e5ef'}}>
<div className="card-header">
<div className="card-heading">
<h4 className="card-title" style={{textTransform: 'none'}}>Set your own URL</h4>
</div>
</div>
<div className="card-body">
<div className="form-group">
<label htmlFor="exampleInputEmail1">Enter your full URL <span
className={`${(props.errors.url && props.touched.url) && 'text-danger'}`}>{props.errors.url}</span></label>
<input value={props.values.url} onChange={(e)=>{props.handleChange(e); handleExternalURLChanged(e)}} type="text"
className="form-control" id="url" aria-describedby="url"
placeholder="https://example.mysite.com"/>
</div>
<div style={{width: '100%', textAlign: 'right'}}>
<button
type="submit"
disabled={setURL.isPending || externalURLChanged}
className="btn btn-primary"
>
{setURL.isPending ? 'Loading...' : 'Submit'}
</button>
</div>
</div>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={handleSubmit}
>
{(props) => {
return (
<Form className="w-full">
<div
className="card card-statistics"
style={{ backgroundColor: "#b6e5ef" }}
>
<div className="card-header">
<div className="card-heading">
<h4
className="card-title"
style={{ textTransform: "none" }}
>
Set your own URL
</h4>
</div>
</div>
<div className="card-body">
<div className="form-group">
<label htmlFor="exampleInputEmail1">
Enter your full URL{" "}
<span
className={`${props.errors.url && props.touched.url && "text-danger"}`}
>
{props.errors.url}
</span>
</label>
<input
value={props.values.url}
onChange={(e) => {
props.handleChange(e);
handleExternalURLChanged(e);
}}
type="text"
className="form-control"
id="url"
aria-describedby="url"
placeholder="https://example.mysite.com"
/>
</div>
<div style={{ width: "100%", textAlign: "right" }}>
<button
type="submit"
disabled={setURL.isPending || externalURLChanged}
className="btn btn-primary"
>
{setURL.isPending ? "Loading..." : "Submit"}
</button>
</div>
</div>
{setURL.error &&
<div className="col-12">
<p className='text-danger'>{setURL.error.message}</p>
</div>
}
{setURL.isSuccess &&
<div className="col-12">
<p className='text-success'>{'Completed successfully'}</p>
</div>
}
<div style={{backgroundColor: '#94b8c0', borderRadius: '10px', padding: '10px'}}>
Final steps to configure your URL:<br/>
DNS:<br/>
DNS:<br/>
DNS:<br/>
</div>
</div>
</Form>
);
}}
</Formik>
{setURL.error && (
<div className="col-12">
<p className="text-danger">{setURL.error.message}</p>
</div>
)}
{setURL.isSuccess && (
<div className="col-12">
<p className="text-success">{"Completed successfully"}</p>
</div>
)}
<div
style={{
backgroundColor: "#148399",
borderRadius: "10px",
padding: "10px",
color: "white",
fontWeight: "bolder",
fontSize: "14px",
}}
>
To link your domain to your website, update your DNS record to
point to our hosting provider.
<br />
<hr />
Enter the following DNS servers in your domain settings
<br />
{process.env.REACT_APP_DNS1}
<br />
{process.env.REACT_APP_DNS2}
<hr />
<Link
target="_blank"
to={process.env.REACT_APP_DNS_LINK}
style={{ color: "#f6f5f6" }}
>
Click here for detailed instructions.
</Link>
<hr />
After updating your DNS settings, click Start Verification.
You will receive a status notification within 24 hours.
</div>
</div>
</Form>
);
}}
</Formik>
</>
}
);
};
export default URLConfiguration
export default URLConfiguration;
@@ -0,0 +1,168 @@
import React, { useRef, useState } from 'react'
import { Modal } from "bootstrap";
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query';
import queryKeys from '../../../services/queryKeys';
import {getMediaFileList, templateMediaSet} from '../../../services/services';
export default function UploadModal({productId, selectedSectionDetails}) {
const queryClient = useQueryClient()
const [selectedFile, setSelectedFile] = useState('')
const handleSelectedFile = (file_uid) => {
// if(selectedFile.length && selectedFile.includes(file_uid)){
// let indexOfItem = selectedFile.indexOf(file_uid)
// let oldFiles = [...selectedFile]
// oldFiles.splice(indexOfItem, 1)
// setSelectedFile(oldFiles)
// }else{
// setSelectedFile(prev => [...prev, file_uid])
// }
setSelectedFile(file_uid)
}
const modalRef = useRef(null)
const dismissModal = () => {
const body = document.querySelector('body')
body.removeAttribute('style')
// body.classList.toggle('modal-open')
const modalBackdrop = document.querySelectorAll('.modal-backdrop')
modalBackdrop.forEach(item => {
if (item) {
item.remove();
}
})
const modal = Modal.getInstance(modalRef.current);
modal && modal.hide();
};
const changeTemplateMediaSet = useMutation({
mutationFn: (fields) => {
if(!fields.file_uid){
throw({message: 'Please select a file first!'})
}
return templateMediaSet(fields)
},
onSuccess: (res) => {
// console.log('res', res.data)
if(res.data.resultCode != '0'){
throw({message: res?.data?.resultDescription})
}
setSelectedFile('')
dismissModal() // to close modal
queryClient.refetchQueries({
queryKey: [...queryKeys.myTemplateConfig],
// type: 'active',
// exact: true,
})
},
onSettled: () => {
setTimeout(()=>{
changeTemplateMediaSet.reset()
}, 3000)
}
})
const handleUpload = () => {
let reqData = {
token: localStorage.getItem("token"), // USER TOKEN
uid: localStorage.getItem("uid"), // USER UID
product_id: productId,
file_uid: selectedFile,
image_id: selectedSectionDetails?.id
};
// console.log('reqData', reqData)
changeTemplateMediaSet.mutate(reqData)
}
const {data, isFetching, isError, error} = useQuery({
queryKey: queryKeys.my_files,
queryFn: () => {
let reqData = {
token: localStorage.getItem('token'), // USER TOKEN
uid: localStorage.getItem('uid') // USER UID
}
return getMediaFileList(reqData)
}
})
const mediaFileList = data?.data
// console.log('mediaFileList', mediaFileList) //file_list
return (
<>
{/* Vertical Center Modal */}
<div ref={modalRef} className="modal fade" id="verticalCenter" tabIndex="-1" role="dialog" aria-hidden="false">
<div className="modal-50 modal-dialog modal-dialog-centered modal-50" role="document">
<div className="modal-content">
<div className="modal-header">
<h5 className="modal-title" style={{fontSize: '18px'}} id="verticalCenterTitle">Select New Picture</h5>
<button type="button" className="close" data-bs-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div className="modal-body">
{isFetching ?
<>
<div className="col-12">
<p className='text-mute'>Loading...</p>
</div>
</>
: isError ?
<div className="col-12">
<p className='text-danger'>{error?.message}</p>
</div>
:
<div className='row g-5 justify-content-center'>
{mediaFileList?.file_list?.length ?
<>
{mediaFileList?.file_list?.map(item => {
const file_url = (mediaFileList?.media_server + "/" + item?.file_group + "/" + item?.file_uid + "/" + item.filename).toLowerCase();
return(
<div onClick={()=>handleSelectedFile(item?.file_uid)} className={`p-3 text-center col-4`} style={{cursor: 'pointer'}}>
<div className={`p-3 ${(selectedFile == item?.file_uid) && 'bg-light'}`} style={{Height: '250px'}}>
<img key={item?.file_uid} className="img-fluid" style={{}} src={file_url} alt='file-image' />
</div>
</div>
)
})
}
</>
:
<p>No File(s) found!</p>
}
</div>
}
{changeTemplateMediaSet.error &&
<>
<div className="col-12">
<p className='text-danger'>{changeTemplateMediaSet.error.message}</p>
</div>
</>
}
{changeTemplateMediaSet.isSuccess &&
<>
<div className="col-12">
<p className='text-success'>{'subscription is successful'}</p>
</div>
</>
}
</div>
<div className="modal-footer">
<button type="button" className="btn btn-danger" data-bs-dismiss="modal">Cancel</button>
{selectedFile &&
<button type="button" className="btn btn-primary" disabled={changeTemplateMediaSet?.isPending || changeTemplateMediaSet?.isSuccess} onClick={handleUpload}>{changeTemplateMediaSet?.isPending ? 'Applying...' : changeTemplateMediaSet?.isSuccess ? 'Applied' : 'Apply'}</button>
}
</div>
</div>
</div>
</div>
{/* END of Vertical Center Modal */}
</>
)
}
@@ -0,0 +1,89 @@
import React, {useState} from "react";
import {useQuery} from "@tanstack/react-query";
import {getPaymentReports} from "../../services/services";
import queryKeys from "../../services/queryKeys";
export default function PaymentReportTable() {
const [page, setPage] = useState(0)
const {data, isFetching, isError, error} = useQuery({
queryKey: [...queryKeys.payment_report, page],
queryFn: () => {
let reqData = {
token: localStorage.getItem('token'), // USER TOKEN
uid: localStorage.getItem('uid'), // USER UID
page
}
return getPaymentReports(reqData)
},
staleTime: 0
})
// console.log('DATA', data?.data)
const paymentReportData = data?.data?.payment || []
return (
<>
<div className="row">
<div className="col-md-12 m-b-30">
<div className="d-block d-sm-flex flex-nowrap align-items-center">
<div className="page-title mb-2 mb-sm-0">
<h4>Payments Report</h4>
</div>
</div>
</div>
</div>
<div className="row">
{isFetching ?
<>
<div className="col-12">
<p className='text-mute'>Loading...</p>
</div>
</>
: isError ?
<div className="col-12">
<p className='text-danger'>{error?.message}</p>
</div>
:
<div className="col-lg-12">
<div className="card card-statistics">
<div className="card-body">
<div className="export-table-wrapper table-responsive">
<table id="export-table" className="table table-bordered">
<thead className="thead-light">
<tr>
<th>Added</th>
<th>Name</th>
<th>Amount</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{paymentReportData.length > 0 ? paymentReportData.map((item, index) => {
return (
<tr key={index}>
<td>{item?.added}</td>
<td>{item?.option_name}</td>
<td>{item?.currency}{item?.amount}</td>
<td>{item?.status}</td>
</tr>
)
})
:
<tr>
<td colSpan={4} className='text-center'>No data found</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</div>
}
</div>
</>
)
}
@@ -0,0 +1,90 @@
import React, {useState} from "react";
import {useQuery} from "@tanstack/react-query";
import {getProductReports} from "../../services/services";
import queryKeys from "../../services/queryKeys";
export default function ProductReportTable() {
const [page, setPage] = useState(0)
const {data, isFetching, isError, error} = useQuery({
queryKey: [...queryKeys.product_report, page],
queryFn: () => {
let reqData = {
token: localStorage.getItem('token'), // USER TOKEN
uid: localStorage.getItem('uid'), // USER UID
page
}
return getProductReports(reqData)
},
staleTime: 0
})
// console.log('DATA', data?.data)
const productReportData = data?.data?.product || []
return (
<>
<div className="row">
<div className="col-md-12 m-b-30">
<div className="d-block d-sm-flex flex-nowrap align-items-center">
<div className="page-title mb-2 mb-sm-0">
<h4>Products Report</h4>
</div>
</div>
</div>
</div>
<div className="row">
{isFetching ?
<>
<div className="col-12">
<p className='text-mute'>Loading...</p>
</div>
</>
: isError ?
<div className="col-12">
<p className='text-danger'>{error?.message}</p>
</div>
:
<div className="col-lg-12">
<div className="card card-statistics">
<div className="card-body">
<div className="export-table-wrapper table-responsive">
<table id="export-table" className="table table-bordered">
<thead className="thead-light">
<tr>
<th>ID</th>
<th>Added</th>
<th>Name</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{productReportData.length > 0 ? productReportData.map((item, index) => {
return (
<tr key={index}>
<td>{item?.product_id}</td>
<td>{item?.added}</td>
<td>{item?.product_name}</td>
<td>{item?.status}</td>
</tr>
)
})
:
<tr>
<td colSpan={4} className='text-center'>No data found</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</div>
}
</div>
</>
)
}
+82 -45
View File
@@ -1,62 +1,99 @@
import React from "react";
import React, { useState } from "react";
import BreadcrumbComBS from "../breadcrumb/BreadcrumbComBS";
import {useQuery} from "@tanstack/react-query";
import queryKeys from "../../services/queryKeys";
import {getReportsTopicsList} from "../../services/services";
import PaymentReportTable from "./PaymentReportTable";
import ProductReportTable from "./ProductReportTable";
import sortArrayByListOrder from "../../helpers/sortArrayByLIstOrder";
import SystemReportTable from "./SystemReportTable";
export default function Reports(){
export default function Reports() {
return(
const [activeTab, setActiveTab] = useState('payment')
const {data, isFetching, isError, error} = useQuery({
queryKey: queryKeys.topics,
queryFn: () => {
let reqData = {
token: localStorage.getItem('token'), // USER TOKEN
uid: localStorage.getItem('uid') // USER UID
}
return getReportsTopicsList(reqData)
}
})
const reportTopicList = data?.data?.topics?.topics;
const renderedTable = {
payment: <PaymentReportTable />,
product: <ProductReportTable />,
system: <SystemReportTable />
}
const sortedReportTopicList= sortArrayByListOrder(reportTopicList ? reportTopicList : []) // SORTED SETTINGSCONFIG
return (
<>
<BreadcrumbComBS title='Reports' paths={['Dashboard', 'Reports']} />
<BreadcrumbComBS title='Reports' paths={['Dashboard', 'Reports']}/>
<div className="row">
{isFetching ?
<>
<div className="col-12">
<p className='text-mute'>Loading...</p>
</div>
</>
: isError ?
<div className="col-12">
<p className='text-danger'>{error?.message}</p>
</div>
:
<div>
<div>
<div className="card card-statistics" style={{minHeight: '500px'}}>
{/*<div className="card-header">*/}
{/* <div className="card-heading">*/}
{/* <h4 className="card-title"> Tab vertical </h4>*/}
{/* </div>*/}
{/*</div>*/}
<div className="card-body">
<div className="tab tab-vertical">
{/* <!-- Tabs --> */}
<ul className="nav nav-tabs" id="myTab" role="tablist">
{sortedReportTopicList && sortedReportTopicList.map((item, index) => {
return (
<li key={index} className="nav-item" role="presentation" onClick={() => setActiveTab(item?.url)}>
<a className={`nav-link ${index == 0 && 'active'}`} id={`tab-${item?.url}`} href="#" data-bs-toggle="tab" data-bs-target={`#content-${item?.url}`} type="button" role="tab">
{item?.name}
</a>
</li>
)
})}
</ul>
<div>
<div className="card card-statistics" style={{minHeight:'550px'}}>
{/*<div className="card-header">*/}
{/* <div className="card-heading">*/}
{/* <h4 className="card-title"> Tab vertical </h4>*/}
{/* </div>*/}
{/*</div>*/}
<div className="card-body">
<div className="tab tab-vertical">
<ul className="nav nav-tabs" role="tablist">
<li className="nav-item">
<a className="nav-link active show" id="home-09-tab" data-toggle="tab" href="#home-09" role="tab" aria-controls="home-09" aria-selected="true"> Home</a>
</li>
<li className="nav-item">
<a className="nav-link" id="profile-09-tab" data-toggle="tab" href="#profile-09" role="tab" aria-controls="profile-09" aria-selected="false"> Profile </a>
</li>
<li className="nav-item">
<a className="nav-link" id="portfolio-09-tab" data-toggle="tab" href="#portfolio-09" role="tab" aria-controls="portfolio-09" aria-selected="false">Portfolio </a>
</li>
<li className="nav-item">
<a className="nav-link" id="contact-09-tab" data-toggle="tab" href="#contact-09" role="tab" aria-controls="contact-09" aria-selected="false"> Contact </a>
</li>
</ul>
<div className="tab-content">
<div className="tab-pane fade active show" id="home-09" role="tabpanel" aria-labelledby="home-09-tab">
<p>Positive pleasure-oriented goals are much more powerful motivators than negative fear-based ones. Although each is successful separately, the right combination of both is the most powerful motivational force known to humankind.Make a list of your achievements toward your long-term goal and remind yourself that intentions dont count, only actions.</p>
</div>
<div className="tab-pane fade" id="profile-09" role="tabpanel" aria-labelledby="profile-09-tab">
<p>Reflect and experiment until you find the right combination of motivators for your personality and your personal goals. Do it today. Remind yourself of someone you know who died suddenly and the fact that there is no guarantee that tomorrow will come.</p>
</div>
<div className="tab-pane fade" id="portfolio-09" role="tabpanel" aria-labelledby="portfolio-09-tab">
<p>Commitment is something that comes from understanding that everything has its price and then having the willingness to pay that price. This is important because nobody wants to put significant effort into something, only to find out after the fact that the price was too high. We all know people who live this truth.Give yourself the power of responsibility.</p>
</div>
<div className="tab-pane fade" id="contact-09" role="tabpanel" aria-labelledby="contact-09-tab">
<p>I truly believe Augustines words are true and if you look at history you know it is true. There are many people in the world with amazing talents who realize only a small percentage of their potential. We all know people who live this truth.Give yourself the power of responsibility. Remind yourself the only thing stopping you is yourself.</p>
</div>
{/* <!-- Tab Content --> */}
{/* <div className="tab-content">
{sortedReportTopicList && sortedReportTopicList.map((item, index) => {
return (
<div key={index} className={`tab-pane fade show ${index == 0 && 'active'}`} id={`content-${item?.url}`} role="tabpanel" style={{minHeight: '400px'}}>
{renderedTable[item?.url]}
</div>
)
})}
</div> */}
<div className="tab-content">
<div className={`tab-pane fade show active`} style={{minHeight: '400px'}}>
{renderedTable[activeTab]}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
}
</div>
</>
)
@@ -0,0 +1,90 @@
import React, {useState} from "react";
import {useQuery} from "@tanstack/react-query";
import {getSystemReports} from "../../services/services";
import queryKeys from "../../services/queryKeys";
export default function SystemReportTable() {
const [page, setPage] = useState(0)
const {data, isFetching, isError, error} = useQuery({
queryKey: [...queryKeys.system_report, page],
queryFn: () => {
let reqData = {
token: localStorage.getItem('token'), // USER TOKEN
uid: localStorage.getItem('uid'), // USER UID
page
}
return getSystemReports(reqData)
},
staleTime: 0
})
// console.log('DATA', data?.data)
const systemReportData = data?.data?.system || []
return (
<>
<div className="row">
<div className="col-md-12 m-b-30">
<div className="d-block d-sm-flex flex-nowrap align-items-center">
<div className="page-title mb-2 mb-sm-0">
<h4>Systems Report</h4>
</div>
</div>
</div>
</div>
<div className="row">
{isFetching ?
<>
<div className="col-12">
<p className='text-mute'>Loading...</p>
</div>
</>
: isError ?
<div className="col-12">
<p className='text-danger'>{error?.message}</p>
</div>
:
<div className="col-lg-12">
<div className="card card-statistics">
<div className="card-body">
<div className="export-table-wrapper table-responsive">
<table id="export-table" className="table table-bordered">
<thead className="thead-light">
<tr>
<th>Added</th>
<th>Action Name</th>
<th>Description</th>
<th>Updated</th>
</tr>
</thead>
<tbody>
{systemReportData.length > 0 ? systemReportData.map((item, index) => {
return (
<tr key={index}>
<td>{item?.added}</td>
<td>{item?.action_name}</td>
<td>{item?.status_description}</td>
<td>{item?.updated}</td>
</tr>
)
})
:
<tr>
<td colSpan={4} className='text-center'>No data found</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</div>
}
</div>
</>
)
}
+16 -16
View File
@@ -8,12 +8,12 @@ import queryKeys from '../../services/queryKeys';
const linksValidationSchema = Yup.object().shape({
// facebook_url: Yup.string().required("facebook is required"),
// twitter_url: Yup.string().required("twitter is required"),
// blogger_url: Yup.string().required("blog is required"),
// google_url: Yup.string().required("google is required"),
// linked_url: Yup.string().required("linkedin is required"),
// website_url: Yup.string().required("website is required"),
facebook_url: Yup.string().matches(/^https:\/\/.+/,"URL must start with https://"),
twitter_url: Yup.string().matches(/^https:\/\/.+/,"URL must start with https://"),
blogger_url: Yup.string().matches(/^https:\/\/.+/,"URL must start with https://"),
google_url: Yup.string().matches(/^https:\/\/.+/,"URL must start with https://"),
linked_url: Yup.string().matches(/^https:\/\/.+/,"URL must start with https://"),
website_url: Yup.string().matches(/^https:\/\/.+/,"URL must start with https://"),
})
export default function LinksForm({data}) {
@@ -73,10 +73,10 @@ export default function LinksForm({data}) {
let reqData = {
token: localStorage.getItem('token'), // USER TOKEN
uid: localStorage.getItem('uid'), // USER UID
...infoToUpdate
url_list: {...infoToUpdate}
}
console.log(reqData)
// updateLinksMutation.mutate(reqData)
// console.log(reqData)
updateLinksMutation.mutate(reqData)
}
return (
@@ -90,31 +90,31 @@ export default function LinksForm({data}) {
return (
<Form className=''>
<div className="form-group">
<label htmlFor="fb">Facebook URL: {(props.errors.facebook_url && props.touched.facebook_url) && <span className="text-danger">*</span>}</label>
<label htmlFor="fb">Facebook URL: {(props.errors.facebook_url && props.touched.facebook_url) && <span className="text-danger">{props.errors.facebook_url}</span>}</label>
<input type="text" className="form-control" name="facebook_url" value={props.values?.facebook_url} onChange={props.handleChange} />
</div>
<div className="form-group">
<label htmlFor="tr">Twitter URL: {(props.errors.twitter_url && props.touched.twitter_url) && <span className="text-danger">*</span>}</label>
<label htmlFor="tr">Twitter URL: {(props.errors.twitter_url && props.touched.twitter_url) && <span className="text-danger">{props.errors.twitter_url}</span>}</label>
<input type="text" className="form-control" name="twitter_url" value={props.values?.twitter_url} onChange={props.handleChange} />
</div>
<div className="form-group">
<label htmlFor="br">Blogger URL: {(props.errors.blogger_url && props.touched.blogger_url) && <span className="text-danger">*</span>}</label>
<label htmlFor="br">Blogger URL: {(props.errors.blogger_url && props.touched.blogger_url) && <span className="text-danger">{props.errors.blogger_url}</span>}</label>
<input type="text" className="form-control" name="blogger_url" value={props.values?.blogger_url} onChange={props.handleChange} />
</div>
<div className="form-group">
<label htmlFor="go">Google+ URL: {(props.errors.google_url && props.touched.google_url) && <span className="text-danger">*</span>}</label>
<label htmlFor="go">Google+ URL: {(props.errors.google_url && props.touched.google_url) && <span className="text-danger">{props.errors.google_url}</span>}</label>
<input type="text" className="form-control" name="google_url" value={props.values?.google_url} onChange={props.handleChange} />
</div>
<div className="form-group">
<label htmlFor="li">LinkedIn URL: {(props.errors.linked_url && props.touched.linked_url) && <span className="text-danger">*</span>}</label>
<label htmlFor="li">LinkedIn URL: {(props.errors.linked_url && props.touched.linked_url) && <span className="text-danger">{props.errors.linked_url}</span>}</label>
<input type="text" className="form-control" name="linked_url" value={props.values?.linked_url} onChange={props.handleChange} />
</div>
<div className="form-group">
<label htmlFor="we">Website URL: {(props.errors.website_url && props.touched.website_url) && <span className="text-danger">*</span>}</label>
<label htmlFor="we">Website URL: {(props.errors.website_url && props.touched.website_url) && <span className="text-danger">{props.errors.website_url}</span>}</label>
<input type="text" className="form-control" name="website_url" value={props.values?.website_url} onChange={props.handleChange} />
</div>
<div style={{textAlign: "right"}}>
@@ -137,7 +137,7 @@ export default function LinksForm({data}) {
</button>
</div>
<div className="modal-body">
<h5 className="text-center" style={{fontSize: '18px'}}>Are you sure, you want to update? gg</h5>
<h5 className="text-center" style={{fontSize: '18px'}}>Are you sure, you want to update?</h5>
{(updateLinksMutation.error || updateLinksMutation.isSuccess) && (
<div className="col-12">
<p className={`p-2 text-center ${updateLinksMutation.isSuccess ? 'text-success' : 'text-danger'}`}>
+1
View File
@@ -72,6 +72,7 @@ export default function ProfileForm({data}) {
const handleSetInfoToUpdate = (values, helpers) => {
delete values.email
delete values.country
delete values?.picture
setInfoToUpdate(values)
var modal = new Modal(document.getElementById('modal'));
modal.show();
+18 -11
View File
@@ -1,17 +1,21 @@
import React, { memo, useRef, useState } from 'react'
// import { useSelector } from 'react-redux';
import { useSelector, useDispatch } from 'react-redux';
import getImage from '../../utils/getImage';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { uploadProfileImg } from '../../services/services';
import queryKeys from '../../services/queryKeys';
import { updateUserDetails } from '../../store/UserDetails'
const ProfileImage = memo(({intialData}) => {
const dispatch = useDispatch()
const queryClient = useQueryClient()
const [selectedImg, setSelectedImg] = useState(null)
// const {userDetails} = useSelector((state) => state?.userDetails); // CHECKS FOR ACTIVE USER DETAILS
const avtarImage = "avtar/merms-user.png";
// browser profile img
@@ -26,23 +30,26 @@ const ProfileImage = memo(({intialData}) => {
const uploadProfileMutation = useMutation({
mutationFn: (fields) => {
if(!fields.img){
if(!fields.file){
throw new Error('Please, select an image')
}
return uploadProfileImg(fields)
},
onSuccess: (res) => {
if(res.data.resultCode != '0'){
throw({message: res?.data?.resultDescription ? res?.data?.resultDescription : 'An error occured'})
console.log('res', res)
if(res?.data?.picture){
dispatch(updateUserDetails({ picture: res?.data?.picture }));
}
// if(res.data.resultCode != '0'){
// throw({message: res?.data?.resultDescription ? res?.data?.resultDescription : 'An error occured'})
// }
// const account_name = res?.data?.personal_data?.account_name
// dispatch(updateUserDetails({ account_name }));
},
onSettled: ()=>{
setTimeout(() => {
queryClient.refetchQueries({
queryKey: [...queryKeys.profile_data], // type: 'active', // exact: true,
})
// queryClient.refetchQueries({
// queryKey: [...queryKeys.profile_data], // type: 'active', // exact: true,
// })
uploadProfileMutation.reset()
}, 3000);
}
@@ -51,8 +58,8 @@ const ProfileImage = memo(({intialData}) => {
const proceedToUpload = () => {
let reqData = {
token: localStorage.getItem('token'), // USER TOKEN
uid: localStorage.getItem('uid'), // USER UID
img: selectedImg
member_uid: localStorage.getItem('uid'), // USER UID
file: selectedImg
}
// console.log('reqData', reqData)
uploadProfileMutation.mutate(reqData)
@@ -95,7 +102,7 @@ const ProfileImage = memo(({intialData}) => {
{selectedImg &&
<div>
<button onClick={proceedToUpload} disabled={uploadProfileMutation.isSuccess || uploadProfileMutation.isPending} className="btn btn-light text-primary mb-2">
{uploadProfileMutation.isPaused ? 'Upload...' : 'Upload New Avatar'}
{uploadProfileMutation.isPending ? 'Upload...' : 'Upload New Avatar'}
</button>
</div>
}
+1 -1
View File
@@ -1,6 +1,6 @@
import React, { useMemo, useState } from "react";
import BreadcrumbComBS from "../breadcrumb/BreadcrumbComBS";
import getImage from "../../utils/getImage";
//import getImage from "../../utils/getImage";
import queryKeys from "../../services/queryKeys";
import { useQuery } from "@tanstack/react-query";
import { profileDetails } from "../../services/services";
+1 -1
View File
@@ -57,7 +57,7 @@ export default function Subscription() {
<h2 className="text-primary pt-3">{currentSubscription?.display_name}</h2>
</div>
<div className="pt-2" style={{textAlign: 'left'}}>
<div style={{fontSize: '12px', fontWeight: 'bolder' , color: "#3E3699" }}>
<div style={{fontSize: '12px', fontWeight: 'bolder', color: "#3E3699"}}>
Next Payment: {getDateTimeFromDateString(currentSubscription?.next_payment)}
</div>
</div>
+17
View File
@@ -0,0 +1,17 @@
function sortArrayByListOrder(data) {
// Convert object to array if needed
const array = Array.isArray(data)
? [...data]
: Object.values(data);
// Sort ascending by list_order
array.sort((a, b) => {
const orderA = a.list_order ?? 0;
const orderB = b.list_order ?? 0;
return orderA - orderB;
});
return array;
}
export default sortArrayByListOrder
-1
View File
@@ -6,7 +6,6 @@ import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
//import reportWebVitals from './reportWebVitals';
import 'bootstrap/dist/css/bootstrap.css';
import 'bootstrap/dist/js/bootstrap.min.js'
+1
View File
@@ -11,6 +11,7 @@ const siteLinks = {
comments: '/comments',
reports: '/reports',
subscription: '/subscription',
my_media: '/my-files',
subscription_success:'/subscription-success',
subscribe: '/subscribe',
onboard: '/subscription',
+6
View File
@@ -8,9 +8,15 @@ const queryKeys = {
recentAction: ['recent-action'],
settingsData: ['settings_data'],
myProductConfig: ['myproduct_config'],
myTemplateConfig: ['mytemplate_config'],
productTemplateData: ['product_template_data'],
subscriptions: ['subscriptions'],
profile_data: ['profile_data'],
my_files: ['my_files'],
topics: ['topics'],
payment_report: ['payment_report'],
product_report: ['product_report'],
system_report: ['system_report'],
dashboard: ['dashboard'],
topBar: ['top-bar'],
+67 -7
View File
@@ -1,10 +1,9 @@
import axios from "axios"
axios.interceptors.request.use(
config => {
config.headers = {
Accept: "application/json",
// Accept: "application/json",
"Access-Control-Allow-Origin": "*",
// "Access-Control-Expose-Headers": "Access-Control-Allow-Origin",
// "Access-Control-Allow-Headers": "Origin, X-API-KEY, X-Requested-With, Content-Type, Accept, Access-Control-Request-Method, Access-Control-Allow-Headers, Authorization, observe, enctype, Content-Length, X-Csrf-Token",
@@ -35,8 +34,8 @@ const postAuxEnd = (path, postData, media=false) => {
return axios.post(`${basePath}${path}`, newPostData).then(res => {
return res
}).catch(err => {
// throw new Error(err.response.data.error_message);
throw new Error(err);
throw new Error(err.response.data.error_message);
// throw new Error(err);
})
}
@@ -106,7 +105,7 @@ export const updateLinks = (reqData) => {
let postData = {
...reqData,
}
return null //postAuxEnd(`/panel/account/links-update`, postData, false)
return postAuxEnd(`/panel/account/profilelinks-update`, postData, false)
}
// FUNCTION TO GET PRODUCT BY ID
@@ -245,6 +244,14 @@ export const getProductColorStyles = (reqData) => {
}
return postAuxEnd(`/panel/account/products/color-styles`, postData, false)
}
export const getTemplateConfig = (reqData) => {
let postData = {
...reqData,
}
return postAuxEnd(`/panel/myproduct/template-config`, postData, false)
}
// FUNCTION TO ACTIVATE TEMPLATE
export const activateTemplate = (reqData) => {
let postData = {
@@ -277,6 +284,59 @@ export const getSubscriptions = (reqData) => {
return postAuxEnd(`/panel/subscription/products`, postData, false)
}
export const getMediaFileList = (reqData) => {
let postData = {
...reqData,
}
return postAuxEnd(`/panel/account/media-files`, postData, false)
}
export const uploadFile = (reqData) => {
let postData = {
...reqData,
}
return postAuxEnd(`/upload/webfiles`, postData, true)
}
// FUNCTION TO CHANGE TEMPLATE MEDIA SET
export const templateMediaSet = (reqData) => {
let postData = {
...reqData,
}
return postAuxEnd(`/panel/myproduct/template-media-set`, postData, false)
}
export const getReportsTopicsList = (reqData) => {
let postData = {
...reqData,
}
return postAuxEnd(`/panel/report/topics`, postData, false)
}
// FUNCTION TO GET PAYMENT REPORTS
export const getPaymentReports = (reqData) => {
let postData = {
...reqData,
}
return postAuxEnd(`/panel/report/item/payment`, postData, false)
}
// FUNCTION TO GET PRODUCT REPORTS
export const getProductReports = (reqData) => {
let postData = {
...reqData,
}
return postAuxEnd(`/panel/report/item/product`, postData, false)
}
// FUNCTION TO GET SYSTEM REPORTS
export const getSystemReports = (reqData) => {
let postData = {
...reqData,
}
return postAuxEnd(`/panel/report/item/system`, postData, false)
}
// FUNCTION TO GET COMMON PRACTICE
export const getCommonPractice = (reqData) => {
let postData = {
@@ -298,8 +358,8 @@ export const uploadProfileImg = (reqData) => {
let postData = {
...reqData,
}
throw new Error('Opps')
// return postAuxEnd(`/panel/account/profile-update`, postData, true)
return postAuxEnd(`/upload/profile-picture`, postData, true)
// throw new Error('Opps')
}
+1 -1
View File
@@ -9,7 +9,7 @@ export const userSlice = createSlice({
initialState,
reducers: {
updateUserDetails: (state, action) => {
state.userDetails = { ...action.payload };
state.userDetails = { ...state.userDetails, ...action.payload };
},
},
});
+8
View File
@@ -0,0 +1,8 @@
import React from 'react'
import MyMedia from "../component/mymedia/MyMedia";
export default function MyMediaPage() {
return (
<MyMedia />
)
}