From 8ac3c89b10a16c904ab75ceca71072d4a01fd038 Mon Sep 17 00:00:00 2001 From: rohit Date: Mon, 11 Aug 2025 00:47:45 +0530 Subject: [PATCH] v1.0.0-alpha --- .env | 1 + package-lock.json | 144 ++ package.json | 2 + src/App.tsx | 325 +++-- src/components/ApprovalStatusModal.tsx | 163 +++ src/components/AuthDebug.tsx | 50 + src/components/AuthInitializer.tsx | 40 +- src/components/BlockedAccountModal.tsx | 197 +++ src/components/CookieConsent.tsx | 2 +- src/components/DeveloperFeedback.tsx | 334 +++++ src/components/DraggableFeedback.tsx | 383 ++++++ src/components/InactiveUserModal.tsx | 234 ++++ src/components/Layout/AdminLayout.tsx | 9 +- src/components/Layout/AdminSidebar.tsx | 7 +- src/components/Layout/Layout.tsx | 16 +- src/components/Layout/ResellerSidebar.tsx | 4 +- src/components/Layout/Sidebar.tsx | 6 +- src/components/Modal.tsx | 16 +- src/components/NotificationBell.tsx | 91 ++ src/components/NotificationPanel.tsx | 394 ++++++ src/components/ProtectedRoute.tsx | 39 +- src/components/VendorDetailsModal.tsx | 358 +++++ src/components/VendorRejectionModal.tsx | 96 ++ .../charts/CommissionTrendsChart.tsx | 2 +- src/components/forms/AddResellerForm.tsx | 316 +++-- src/components/forms/ProductForm.tsx | 614 +++++++++ src/index.css | 9 + src/pages/AccountPending.tsx | 217 +++ src/pages/Analytics.tsx | 8 +- .../ApprovedResellers.tsx} | 259 ++-- src/pages/Dashboard.tsx | 37 +- src/pages/Login.tsx | 73 +- src/pages/ProductManagement.tsx | 893 ++++++------ src/pages/Resellers/index.tsx | 1062 +++++++++++--- src/pages/Signup.tsx | 802 +++++++++-- src/pages/SignupStepwise.tsx | 989 +++++++++++++ src/pages/Training.tsx | 93 +- src/pages/admin/Analytics.tsx | 289 ++++ src/pages/admin/ChannelPartners.tsx | 73 +- src/pages/admin/Dashboard.tsx | 471 ++++++- src/pages/admin/Feedback.tsx | 433 ++++++ src/pages/admin/Products.tsx | 920 +++++++++++++ src/pages/admin/RegisteredVendors.tsx | 568 ++++++++ src/pages/admin/Reports.tsx | 379 +++++ src/pages/admin/Resellers.tsx | 520 +++++++ src/pages/admin/Settings.tsx | 617 +++++++++ src/pages/admin/VendorRequests.tsx | 597 ++++---- src/pages/reseller/Instances.tsx | 770 +++++++---- src/pages/reseller/Login.tsx | 56 +- src/pages/reseller/Receipts.tsx | 827 +++++++++++ src/pages/reseller/Signup.tsx | 1223 +++++++++++------ src/services/api.ts | 407 ++++++ src/services/socketService.ts | 187 +++ src/store/index.ts | 4 + src/store/slices/authSlice.ts | 31 +- src/store/slices/authThunks.ts | 47 +- src/store/slices/productSlice.ts | 117 ++ src/store/slices/productThunks.ts | 192 +++ src/store/slices/receiptSlice.ts | 232 ++++ src/store/slices/themeSlice.ts | 6 +- src/types/receipt.ts | 104 ++ src/types/vendor.ts | 41 + src/utils/authTest.ts | 38 + src/utils/validation.ts | 2 +- 64 files changed, 15078 insertions(+), 2358 deletions(-) create mode 100644 .env create mode 100644 src/components/ApprovalStatusModal.tsx create mode 100644 src/components/AuthDebug.tsx create mode 100644 src/components/BlockedAccountModal.tsx create mode 100644 src/components/DeveloperFeedback.tsx create mode 100644 src/components/DraggableFeedback.tsx create mode 100644 src/components/InactiveUserModal.tsx create mode 100644 src/components/NotificationBell.tsx create mode 100644 src/components/NotificationPanel.tsx create mode 100644 src/components/VendorDetailsModal.tsx create mode 100644 src/components/VendorRejectionModal.tsx create mode 100644 src/components/forms/ProductForm.tsx create mode 100644 src/pages/AccountPending.tsx rename src/pages/{Partnerships/index.tsx => ApprovedResellers/ApprovedResellers.tsx} (63%) create mode 100644 src/pages/SignupStepwise.tsx create mode 100644 src/pages/admin/Analytics.tsx create mode 100644 src/pages/admin/Feedback.tsx create mode 100644 src/pages/admin/Products.tsx create mode 100644 src/pages/admin/RegisteredVendors.tsx create mode 100644 src/pages/admin/Reports.tsx create mode 100644 src/pages/admin/Resellers.tsx create mode 100644 src/pages/admin/Settings.tsx create mode 100644 src/pages/reseller/Receipts.tsx create mode 100644 src/services/socketService.ts create mode 100644 src/store/slices/productSlice.ts create mode 100644 src/store/slices/productThunks.ts create mode 100644 src/store/slices/receiptSlice.ts create mode 100644 src/types/receipt.ts create mode 100644 src/types/vendor.ts create mode 100644 src/utils/authTest.ts diff --git a/.env b/.env new file mode 100644 index 0000000..79f1299 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +REACT_APP_API_URL=http://localhost:5000/api diff --git a/package-lock.json b/package-lock.json index 9deae18..446abec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@types/node": "^16.18.126", "@types/react": "^19.1.9", "@types/react-dom": "^19.1.7", + "@types/socket.io-client": "^1.4.36", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.536.0", @@ -31,6 +32,7 @@ "react-router-dom": "^6.30.1", "react-scripts": "5.0.1", "recharts": "^3.1.0", + "socket.io-client": "^4.8.1", "tailwind-merge": "^3.3.1", "typescript": "^4.9.5", "web-vitals": "^2.1.4" @@ -3428,6 +3430,12 @@ "@sinonjs/commons": "^1.7.0" } }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, "node_modules/@standard-schema/spec": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", @@ -4282,6 +4290,12 @@ "@types/send": "*" } }, + "node_modules/@types/socket.io-client": { + "version": "1.4.36", + "resolved": "https://registry.npmjs.org/@types/socket.io-client/-/socket.io-client-1.4.36.tgz", + "integrity": "sha512-ZJWjtFBeBy1kRSYpVbeGYTElf6BqPQUkXDlHHD4k/42byCN5Rh027f4yARHCink9sKAkbtGZXEAmR0ZCnc2/Ag==", + "license": "MIT" + }, "node_modules/@types/sockjs": { "version": "0.3.36", "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", @@ -7494,6 +7508,66 @@ "node": ">= 0.8" } }, + "node_modules/engine.io-client": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz", + "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io-client/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/enhanced-resolve": { "version": "5.18.2", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz", @@ -15911,6 +15985,68 @@ "node": ">=8" } }, + "node_modules/socket.io-client": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", @@ -18435,6 +18571,14 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "license": "MIT" }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index 7d55356..4a6a4ee 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@types/node": "^16.18.126", "@types/react": "^19.1.9", "@types/react-dom": "^19.1.7", + "@types/socket.io-client": "^1.4.36", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.536.0", @@ -26,6 +27,7 @@ "react-router-dom": "^6.30.1", "react-scripts": "5.0.1", "recharts": "^3.1.0", + "socket.io-client": "^4.8.1", "tailwind-merge": "^3.3.1", "typescript": "^4.9.5", "web-vitals": "^2.1.4" diff --git a/src/App.tsx b/src/App.tsx index e0aec9e..5211152 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,6 +3,7 @@ import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-d import { Provider } from 'react-redux'; import { CookiesProvider } from 'react-cookie'; import { store } from './store'; +import { useAppSelector } from './store/hooks'; import { setTheme } from './store/slices/themeSlice'; import ProtectedRoute from './components/ProtectedRoute'; import AuthInitializer from './components/AuthInitializer'; @@ -11,8 +12,8 @@ import Toast from './components/Toast'; // Channel Partner Components import Layout from './components/Layout/Layout'; import Dashboard from './pages/Dashboard'; -import ResellersPage from './pages/Resellers'; -import PartnershipsPage from './pages/Partnerships'; +import ResellerRequestsPage from './pages/Resellers'; +import ApprovedResellersPage from './pages/ApprovedResellers/ApprovedResellers'; import DealsPage from './pages/Deals'; import CommissionsPage from './pages/Commissions'; import ProductManagement from './pages/ProductManagement'; @@ -22,7 +23,7 @@ import Analytics from './pages/Analytics'; import Reports from './pages/Reports'; import Settings from './pages/Settings'; import Login from './pages/Login'; -import Signup from './pages/Signup'; +import Signup from './pages/SignupStepwise'; import VerifyEmail from './pages/VerifyEmail'; // Reseller Components @@ -35,6 +36,7 @@ import ResellerBilling from './pages/reseller/Billing'; import ResellerSupport from './pages/reseller/Support'; import ResellerReports from './pages/reseller/Reports'; import ResellerTraining from './pages/reseller/Training'; +import Receipts from './pages/reseller/Receipts'; import ResellerLayout from './components/Layout/ResellerLayout'; // Admin Components @@ -43,11 +45,39 @@ import AdminDashboard from './pages/admin/Dashboard'; import VendorRequests from './pages/admin/VendorRequests'; import ChannelPartners from './pages/admin/ChannelPartners'; import AdminUsers from './pages/admin/Users'; +import Products from './pages/admin/Products'; +import AdminAnalytics from './pages/admin/Analytics'; +import AdminReports from './pages/admin/Reports'; +import AdminSettings from './pages/admin/Settings'; +import AdminFeedback from './pages/admin/Feedback'; +import RegisteredVendors from './pages/admin/RegisteredVendors'; +import Resellers from './pages/admin/Resellers'; import Unauthorized from './pages/Unauthorized'; import CookieConsent from './components/CookieConsent'; +import AuthDebug from './components/AuthDebug'; +import DeveloperFeedback from './components/DeveloperFeedback'; +import socketService from './services/socketService'; import './index.css'; +// Component to handle role-based redirects +const RoleBasedRedirect: React.FC = () => { + const { user } = useAppSelector((state) => state.auth); + + // Check if user has roles array and find the primary role + const primaryRole = user?.roles?.[0]?.name || user?.role; + + if (primaryRole === 'system_admin') { + return ; + } else if (primaryRole?.startsWith('channel_partner_')) { + return ; + } else if (primaryRole?.startsWith('reseller_')) { + return ; + } else { + return ; + } +}; + // Placeholder components for other pages const PlaceholderPage: React.FC<{ title: string; description: string }> = ({ title, description }) => (
@@ -91,15 +121,25 @@ function App() { }; mediaQuery.addEventListener('change', handleChange); - return () => mediaQuery.removeEventListener('change', handleChange); + + // Initialize socket connection if user is logged in + const token = localStorage.getItem('accessToken'); + if (token) { + socketService.connect(token); + } + + return () => { + mediaQuery.removeEventListener('change', handleChange); + socketService.disconnect(); + }; }, []); return ( - -
+ +
{/* Public Routes */} } /> @@ -107,117 +147,120 @@ function App() { } /> } /> + {/* Root route - Redirect based on user role */} + } /> + {/* Protected Routes - Vendor Only */} - - - - + + + } /> - - - + + + } /> - - - - + + + } /> - - - + + + } /> - - - + + + } /> - - - + + + } /> - - - + + + } /> - - - - + + + - } /> - + - - - + + + - } /> - + - - - + + + - } /> - + - - - + + + - } /> - + - - - + + + - } /> - + - - - + + + - } /> + } /> - - - + + + } /> - - - + + + } /> - - - + + + } /> @@ -258,44 +301,51 @@ function App() { {/* Reseller Dashboard Routes (Separate Service) */} - - - + + + } /> - - - + + + } /> - - - + + + } /> - - - + + + } /> - - - - + + + - } /> + } /> - - - + + + + + } /> + + + + } /> @@ -328,46 +378,95 @@ function App() { } /> + + + + + + } /> + + + + + + } /> + + + + + + } /> + + + + + + } /> + + + + + + } /> + + + + + + } /> + + + + + + } /> - - - + + + } /> - - - + + + } /> - - - + + + } /> - - - + + + } /> - - - + + + } /> - - - + + + } /> } /> - {/* Default Route - Redirect to login */} - } /> + {/* Default Route - Redirect based on user role */} + } /> - - -
-
+ + + + +
+
diff --git a/src/components/ApprovalStatusModal.tsx b/src/components/ApprovalStatusModal.tsx new file mode 100644 index 0000000..6fd1e25 --- /dev/null +++ b/src/components/ApprovalStatusModal.tsx @@ -0,0 +1,163 @@ +import React from 'react'; +import { Clock, AlertCircle, Mail, Phone, X } from 'lucide-react'; + +interface ApprovalStatusModalProps { + isOpen: boolean; + onClose: () => void; + userEmail?: string; + companyName?: string; +} + +const ApprovalStatusModal: React.FC = ({ + isOpen, + onClose, + userEmail, + companyName +}) => { + if (!isOpen) return null; + + return ( +
+
+ {/* Header */} +
+
+
+ +
+
+

+ Account Under Review +

+

+ Your registration is being processed +

+
+
+ +
+ + {/* Content */} +
+
+
+ +
+

+ Thank you for your registration! +

+

+ Your account is currently under review by our administrative team. + This process typically takes 1-3 business days. We'll notify you + via email once your account has been approved. +

+
+
+ + {/* Account Details */} +
+

+ Registration Details +

+
+ {companyName && ( +
+ Company: + {companyName} +
+ )} + {userEmail && ( +
+ Email: + {userEmail} +
+ )} +
+ Status: + Under Review +
+
+
+ + {/* What happens next */} +
+

+ What happens next? +

+
+
+
+ 1 +
+
+

+ Our team reviews your business information and documentation +

+
+
+
+
+ 2 +
+
+

+ We verify your business credentials and compliance requirements +

+
+
+
+
+ 3 +
+
+

+ You'll receive an email notification once approved +

+
+
+
+
+ + {/* Contact Information */} +
+

+ Need help? Contact our support team +

+
+
+ + + support@cloutopiaa.com + +
+
+ + + +1 (555) 123-4567 + +
+
+
+
+
+ + {/* Footer */} +
+ +
+
+
+ ); +}; + +export default ApprovalStatusModal; \ No newline at end of file diff --git a/src/components/AuthDebug.tsx b/src/components/AuthDebug.tsx new file mode 100644 index 0000000..74fd5cd --- /dev/null +++ b/src/components/AuthDebug.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { useAppSelector } from '../store/hooks'; +import { testAuthPersistence } from '../utils/authTest'; + +const AuthDebug: React.FC = () => { + const { isAuthenticated, user, isLoading, token, refreshToken, sessionId } = useAppSelector((state) => state.auth); + + const handleTestAuth = () => { + testAuthPersistence(); + }; + + const handleClearAuth = () => { + localStorage.removeItem('accessToken'); + localStorage.removeItem('refreshToken'); + localStorage.removeItem('sessionId'); + window.location.reload(); + }; + + return ( +
+

Auth Debug

+ +
+
Authenticated: {isAuthenticated ? 'Yes' : 'No'}
+
Loading: {isLoading ? 'Yes' : 'No'}
+
User: {user ? 'Loaded' : 'None'}
+
Token: {token ? 'Present' : 'Missing'}
+
Refresh Token: {refreshToken ? 'Present' : 'Missing'}
+
Session ID: {sessionId ? 'Present' : 'Missing'}
+
+ +
+ + +
+
+ ); +}; + +export default AuthDebug; \ No newline at end of file diff --git a/src/components/AuthInitializer.tsx b/src/components/AuthInitializer.tsx index 3fdfeaa..3bf3985 100644 --- a/src/components/AuthInitializer.tsx +++ b/src/components/AuthInitializer.tsx @@ -1,11 +1,12 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import { useAppDispatch, useAppSelector } from '../store/hooks'; import { getCurrentUser, refreshUserToken } from '../store/slices/authThunks'; -import { setTokens } from '../store/slices/authSlice'; +import { setTokens, logout } from '../store/slices/authSlice'; const AuthInitializer: React.FC<{ children: React.ReactNode }> = ({ children }) => { const dispatch = useAppDispatch(); - const { isAuthenticated, user } = useAppSelector((state) => state.auth); + const { isAuthenticated, user, isLoading } = useAppSelector((state) => state.auth); + const [isInitialized, setIsInitialized] = useState(false); useEffect(() => { const initializeAuth = async () => { @@ -26,29 +27,52 @@ const AuthInitializer: React.FC<{ children: React.ReactNode }> = ({ children }) // Try to get current user try { await dispatch(getCurrentUser()).unwrap(); + console.log('User loaded successfully'); } catch (error) { + console.log('Failed to get current user, trying to refresh token...', error); // If getting user fails, try to refresh token - console.log('Failed to get current user, trying to refresh token...'); try { await dispatch(refreshUserToken()).unwrap(); // Try to get user again after token refresh await dispatch(getCurrentUser()).unwrap(); + console.log('User loaded successfully after token refresh'); } catch (refreshError) { - console.log('Token refresh failed, clearing auth data...'); - // Clear invalid tokens + console.log('Token refresh failed, clearing auth data...', refreshError); + // Clear invalid tokens and logout localStorage.removeItem('accessToken'); localStorage.removeItem('refreshToken'); localStorage.removeItem('sessionId'); + dispatch(logout()); } } + } else { + console.log('No tokens found in localStorage'); } } catch (error) { console.error('Auth initialization error:', error); + // Clear any invalid tokens + localStorage.removeItem('accessToken'); + localStorage.removeItem('refreshToken'); + localStorage.removeItem('sessionId'); + dispatch(logout()); + } finally { + setIsInitialized(true); } }; - initializeAuth(); - }, [dispatch]); + if (!isInitialized) { + initializeAuth(); + } + }, [dispatch, isInitialized]); + + // Show loading while initializing authentication + if (!isInitialized || (isAuthenticated && !user && isLoading)) { + return ( +
+
+
+ ); + } return <>{children}; }; diff --git a/src/components/BlockedAccountModal.tsx b/src/components/BlockedAccountModal.tsx new file mode 100644 index 0000000..37cd152 --- /dev/null +++ b/src/components/BlockedAccountModal.tsx @@ -0,0 +1,197 @@ +import React from 'react'; +import { X, AlertTriangle, Mail, Phone, MessageCircle, Clock, UserCheck, ExternalLink, RefreshCw } from 'lucide-react'; +import { cn } from '../utils/cn'; + +interface BlockedAccountModalProps { + isOpen: boolean; + onClose: () => void; + userEmail: string; + userStatus: string; +} + +const BlockedAccountModal: React.FC = ({ + isOpen, + onClose, + userEmail, + userStatus +}) => { + if (!isOpen) return null; + + const getStatusMessage = () => { + switch (userStatus) { + case 'inactive': + return { + title: 'Account Deactivated', + message: 'Your account has been deactivated by the administrator. This usually happens when your account has been inactive for an extended period or when requested by you.', + icon: , + color: 'red', + bgColor: 'bg-red-50 dark:bg-red-900/20', + borderColor: 'border-red-200 dark:border-red-800', + textColor: 'text-red-800 dark:text-red-200', + actionMessage: 'To reactivate your account, please contact your vendor administrator.' + }; + case 'suspended': + return { + title: 'Account Suspended', + message: 'Your account has been suspended due to policy violations or security concerns. This is a temporary measure to protect our platform and users.', + icon: , + color: 'orange', + bgColor: 'bg-orange-50 dark:bg-orange-900/20', + borderColor: 'border-orange-200 dark:border-orange-800', + textColor: 'text-orange-800 dark:text-orange-200', + actionMessage: 'Please contact your vendor administrator to understand the reason and resolve the issue.' + }; + case 'pending': + return { + title: 'Account Pending Approval', + message: 'Your account is currently pending approval from your vendor administrator. This is normal for new registrations and helps maintain platform security.', + icon: , + color: 'yellow', + bgColor: 'bg-yellow-50 dark:bg-yellow-900/20', + borderColor: 'border-yellow-200 dark:border-yellow-800', + textColor: 'text-yellow-800 dark:text-yellow-200', + actionMessage: 'You will receive an email notification once your account is approved by your vendor administrator.' + }; + default: + return { + title: 'Account Blocked', + message: 'Your account is currently blocked and cannot be used to access the system.', + icon: , + color: 'gray', + bgColor: 'bg-gray-50 dark:bg-gray-900/20', + borderColor: 'border-gray-200 dark:border-gray-800', + textColor: 'text-gray-800 dark:text-gray-200', + actionMessage: 'Please contact your vendor administrator to resolve this issue.' + }; + } + }; + + const statusInfo = getStatusMessage(); + + const handleContactVendor = () => { + const subject = encodeURIComponent(`Account Status Inquiry - ${statusInfo.title}`); + const body = encodeURIComponent( + `Hello Vendor Administrator,\n\nI am inquiring about my account status.\n\nEmail: ${userEmail}\nCurrent Status: ${userStatus}\n\nPlease let me know what additional information you need to resolve this issue.\n\nThank you.` + ); + window.open(`mailto:admin@cloudtopiaa.com?subject=${subject}&body=${body}`, '_blank'); + }; + + const handleContactSupport = () => { + const subject = encodeURIComponent(`Account Status Support - ${statusInfo.title}`); + const body = encodeURIComponent( + `Hello Support Team,\n\nI need assistance with my account status.\n\nEmail: ${userEmail}\nCurrent Status: ${userStatus}\n\nPlease help me understand what I need to do to resolve this.\n\nThank you.` + ); + window.open(`mailto:support@cloudtopiaa.com?subject=${subject}&body=${body}`, '_blank'); + }; + + const handleRefreshPage = () => { + window.location.reload(); + }; + + return ( +
+
+ {/* Header */} +
+
+ {statusInfo.icon} +
+

+ {statusInfo.title} +

+

+ Access Restricted +

+
+
+ +
+ + {/* Content */} +
+
+

+ {statusInfo.message} +

+
+ +
+

+ What you need to do: +

+

+ {statusInfo.actionMessage} +

+ +
+ + + + + +
+
+ +
+

+ Need immediate assistance? +

+
+
+ + Call: +1 (555) 123-4567 +
+
+ + Email: support@cloudtopiaa.com +
+
+ + Support Portal: support.cloudtopiaa.com +
+
+
+
+ + {/* Footer */} +
+ +
+
+
+ ); +}; + +export default BlockedAccountModal; \ No newline at end of file diff --git a/src/components/CookieConsent.tsx b/src/components/CookieConsent.tsx index 5211a11..2a121e4 100644 --- a/src/components/CookieConsent.tsx +++ b/src/components/CookieConsent.tsx @@ -36,7 +36,7 @@ const CookieConsent: React.FC = () => { if (!isVisible) return null; return ( -
+
{/* Backdrop */}
{ + const [isOpen, setIsOpen] = useState(false); + const [tickets, setTickets] = useState([]); + const [currentTicket, setCurrentTicket] = useState>({ + type: 'general', + priority: 'medium', + title: '', + description: '' + }); + const [submitting, setSubmitting] = useState(false); + const [submitted, setSubmitted] = useState(false); + + const ticketTypes = [ + { id: 'bug', label: 'Bug Report', icon: Bug, color: 'text-red-600' }, + { id: 'feature', label: 'Feature Request', icon: Lightbulb, color: 'text-blue-600' }, + { id: 'question', label: 'Question', icon: HelpCircle, color: 'text-green-600' }, + { id: 'general', label: 'General Feedback', icon: Star, color: 'text-yellow-600' } + ]; + + const priorities = [ + { id: 'low', label: 'Low', color: 'text-gray-600' }, + { id: 'medium', label: 'Medium', color: 'text-yellow-600' }, + { id: 'high', label: 'High', color: 'text-orange-600' }, + { id: 'critical', label: 'Critical', color: 'text-red-600' } + ]; + + const handleSubmit = async () => { + if (!currentTicket.title || !currentTicket.description) { + alert('Please fill in all required fields'); + return; + } + + setSubmitting(true); + + try { + const newTicket: FeedbackTicket = { + id: Date.now().toString(), + type: currentTicket.type as 'bug' | 'feature' | 'question' | 'general', + title: currentTicket.title, + description: currentTicket.description, + priority: currentTicket.priority as 'low' | 'medium' | 'high' | 'critical', + status: 'open', + createdAt: new Date().toISOString(), + userAgent: navigator.userAgent, + url: window.location.href, + submittedBy: 'Developer' // This will be overridden by backend with actual user info + }; + + // API call to submit feedback + const response = await fetch(`${process.env.REACT_APP_API_URL || 'http://localhost:5000/api'}/developer-feedback`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${localStorage.getItem('accessToken')}` + }, + body: JSON.stringify({ + type: newTicket.type, + title: newTicket.title, + description: newTicket.description, + priority: newTicket.priority, + userAgent: newTicket.userAgent, + url: newTicket.url, + submittedBy: newTicket.submittedBy + }) + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.message || `HTTP ${response.status}: Failed to submit feedback`); + } + + const result = await response.json(); + if (!result.success) { + throw new Error(result.message || 'Failed to submit feedback'); + } + + setTickets(prev => [newTicket, ...prev]); + setCurrentTicket({ type: 'general', priority: 'medium', title: '', description: '' }); + setSubmitted(true); + + setTimeout(() => setSubmitted(false), 3000); + } catch (error) { + console.error('Error submitting feedback:', error); + alert('Failed to submit feedback. Please try again.'); + } finally { + setSubmitting(false); + } + }; + + const getStatusColor = (status: string) => { + switch (status) { + case 'open': return 'text-blue-600'; + case 'in-progress': return 'text-yellow-600'; + case 'resolved': return 'text-green-600'; + case 'closed': return 'text-gray-600'; + default: return 'text-gray-600'; + } + }; + + const getStatusIcon = (status: string) => { + switch (status) { + case 'open': return ; + case 'in-progress': return ; + case 'resolved': return ; + case 'closed': return ; + default: return ; + } + }; + + return ( + <> + {/* Floating Button */} + + + {/* Modal */} + {isOpen && ( +
+ +
+ {/* Header */} +
+

+ Developer Feedback +

+ +
+ + {/* Content */} +
+ {/* Success Message */} + {submitted && ( +
+
+ + + Feedback submitted successfully! Thank you for your input. + +
+
+ )} + + {/* Ticket Type Selection */} +
+ +
+ {ticketTypes.map((type) => { + const Icon = type.icon; + return ( + + ); + })} +
+
+ + {/* Priority Selection */} +
+ +
+ {priorities.map((priority) => ( + + ))} +
+
+ + {/* Title */} +
+ + setCurrentTicket(prev => ({ ...prev, title: e.target.value }))} + placeholder="Brief description of the issue or request" + className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white dark:bg-gray-700" + /> +
+ + {/* Description */} +
+ +