From aaef6e883bfc17b97c276791d81b438e97c26ce4 Mon Sep 17 00:00:00 2001 From: rohit Date: Wed, 6 Aug 2025 02:40:24 +0530 Subject: [PATCH] auth, integrated --- env.example | 1 + package-lock.json | 27 ++ package.json | 1 + public/index.html | 2 +- src/App.tsx | 286 +++++++++++------- src/components/AuthInitializer.tsx | 56 ++++ src/components/Layout/Sidebar.tsx | 34 +-- src/components/ProtectedRoute.tsx | 76 +++++ src/components/Toast.tsx | 57 ++++ .../reseller/layout/ResellerLayout.tsx | 15 +- .../reseller/layout/ResellerSidebar.tsx | 96 ++++-- src/data/mockData.ts | 28 +- src/index.css | 60 ++++ src/pages/Dashboard.tsx | 10 +- src/pages/Login.tsx | 110 ++++--- src/pages/Signup.tsx | 115 ++++--- src/pages/Unauthorized.tsx | 81 +++++ src/pages/VerifyEmail.tsx | 251 +++++++++++++++ src/pages/reseller/Dashboard.tsx | 17 +- src/pages/reseller/Login.tsx | 72 +++-- src/pages/reseller/Signup.tsx | 75 +++-- src/services/api.ts | 212 +++++++++++++ src/store/slices/authSlice.ts | 25 +- src/store/slices/authThunks.ts | 204 +++++++++++++ src/utils/validation.ts | 29 ++ 25 files changed, 1599 insertions(+), 341 deletions(-) create mode 100644 env.example create mode 100644 src/components/AuthInitializer.tsx create mode 100644 src/components/ProtectedRoute.tsx create mode 100644 src/components/Toast.tsx create mode 100644 src/pages/Unauthorized.tsx create mode 100644 src/pages/VerifyEmail.tsx create mode 100644 src/services/api.ts create mode 100644 src/store/slices/authThunks.ts create mode 100644 src/utils/validation.ts diff --git a/env.example b/env.example new file mode 100644 index 0000000..d513a37 --- /dev/null +++ b/env.example @@ -0,0 +1 @@ +REACT_APP_API_URL=http://localhost:5000/api \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index fa31817..9deae18 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "react": "^19.1.1", "react-cookie": "^8.0.1", "react-dom": "^19.1.1", + "react-hot-toast": "^2.5.2", "react-redux": "^9.2.0", "react-router-dom": "^6.30.1", "react-scripts": "5.0.1", @@ -9301,6 +9302,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/goober": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.16.tgz", + "integrity": "sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==", + "license": "MIT", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -14589,6 +14599,23 @@ "integrity": "sha512-SN/U6Ytxf1QGkw/9ve5Y+NxBbZM6Ht95tuXNMKs8EJyFa/Vy/+Co3stop3KBHARfn/giv+Lj1uUnTfOJ3moFEQ==", "license": "MIT" }, + "node_modules/react-hot-toast": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.5.2.tgz", + "integrity": "sha512-Tun3BbCxzmXXM7C+NI4qiv6lT0uwGh4oAfeJyNOjYUejTsm35mK9iCaYLGv8cBz9L5YxZLx/2ii7zsIwPtPUdw==", + "license": "MIT", + "dependencies": { + "csstype": "^3.1.3", + "goober": "^2.1.16" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", diff --git a/package.json b/package.json index fa5601e..7d55356 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "react": "^19.1.1", "react-cookie": "^8.0.1", "react-dom": "^19.1.1", + "react-hot-toast": "^2.5.2", "react-redux": "^9.2.0", "react-router-dom": "^6.30.1", "react-scripts": "5.0.1", diff --git a/public/index.html b/public/index.html index 1445d14..4f1c266 100644 --- a/public/index.html +++ b/public/index.html @@ -24,7 +24,7 @@ work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> - Cloudtopiaa reseller Dashboard + Cloudtopiaa Connect diff --git a/src/App.tsx b/src/App.tsx index 1a2f5d7..95350f0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,9 +1,12 @@ import React, { useEffect } from 'react'; -import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; +import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'; import { Provider } from 'react-redux'; import { CookiesProvider } from 'react-cookie'; import { store } from './store'; import { setTheme } from './store/slices/themeSlice'; +import ProtectedRoute from './components/ProtectedRoute'; +import AuthInitializer from './components/AuthInitializer'; +import Toast from './components/Toast'; // Channel Partner Components import Layout from './components/Layout/Layout'; @@ -20,6 +23,7 @@ import Reports from './pages/Reports'; import Settings from './pages/Settings'; import Login from './pages/Login'; import Signup from './pages/Signup'; +import VerifyEmail from './pages/VerifyEmail'; // Reseller Components import ResellerLogin from './pages/reseller/Login'; @@ -32,6 +36,7 @@ import ResellerSupport from './pages/reseller/Support'; import ResellerReports from './pages/reseller/Reports'; import ResellerTraining from './pages/reseller/Training'; import ResellerLayout from './components/Layout/ResellerLayout'; +import Unauthorized from './pages/Unauthorized'; import CookieConsent from './components/CookieConsent'; import './index.css'; @@ -84,91 +89,128 @@ function App() { return ( - -
+ + +
- {/* Channel Partner Routes */} + {/* Public Routes */} } /> } /> + } /> + } /> + + {/* Protected Routes - Vendor Only */} - - + + + + + } /> - - + + + + + } /> - - + + + + + } /> - - + + + + + } /> - - + + + + + } /> - - + + + + + } /> - - + + + + + + } /> + + + + + + } /> + + + + + + } /> + + + + + + } /> + + + + + + } /> + + + + + + } /> + + + + + } /> - - - - } /> - - - - } /> - - - - } /> - - - - } /> - - - - } /> - - - - } /> - - + + + + + } /> - - + + + + + } /> - - + + + + + } /> {/* Reseller Routes */} @@ -207,64 +249,88 @@ function App() { {/* Reseller Dashboard Routes (Separate Service) */} - - + + + + + } /> - - + + + + + } /> - - + + + + + } /> - - + + + + + + } /> + + + + + } /> - - - - } /> - - + + + + + } /> - - + + + + + } /> - - + + + + + } /> - - + + + + + } /> - - + + + + + } /> - - + + + + + } /> - - + + + + + } /> @@ -297,16 +363,14 @@ function App() { } /> - {/* Default Route */} - - - - } /> + {/* Default Route - Redirect to login */} + } /> - -
-
+ + +
+
+
); diff --git a/src/components/AuthInitializer.tsx b/src/components/AuthInitializer.tsx new file mode 100644 index 0000000..3fdfeaa --- /dev/null +++ b/src/components/AuthInitializer.tsx @@ -0,0 +1,56 @@ +import React, { useEffect } from 'react'; +import { useAppDispatch, useAppSelector } from '../store/hooks'; +import { getCurrentUser, refreshUserToken } from '../store/slices/authThunks'; +import { setTokens } from '../store/slices/authSlice'; + +const AuthInitializer: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const dispatch = useAppDispatch(); + const { isAuthenticated, user } = useAppSelector((state) => state.auth); + + useEffect(() => { + const initializeAuth = async () => { + try { + // Check for existing tokens in localStorage + const accessToken = localStorage.getItem('accessToken'); + const refreshToken = localStorage.getItem('refreshToken'); + const sessionId = localStorage.getItem('sessionId'); + + if (accessToken && refreshToken && sessionId) { + // Set tokens in store + dispatch(setTokens({ + token: accessToken, + refreshToken, + sessionId, + })); + + // Try to get current user + try { + await dispatch(getCurrentUser()).unwrap(); + } catch (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(); + } catch (refreshError) { + console.log('Token refresh failed, clearing auth data...'); + // Clear invalid tokens + localStorage.removeItem('accessToken'); + localStorage.removeItem('refreshToken'); + localStorage.removeItem('sessionId'); + } + } + } + } catch (error) { + console.error('Auth initialization error:', error); + } + }; + + initializeAuth(); + }, [dispatch]); + + return <>{children}; +}; + +export default AuthInitializer; \ No newline at end of file diff --git a/src/components/Layout/Sidebar.tsx b/src/components/Layout/Sidebar.tsx index 5e995d7..52f861c 100644 --- a/src/components/Layout/Sidebar.tsx +++ b/src/components/Layout/Sidebar.tsx @@ -23,12 +23,14 @@ import { Handshake, Target, TrendingUp, - Package + Package, + User } from 'lucide-react'; import { RootState } from '../../store'; import { toggleTheme } from '../../store/slices/themeSlice'; import { logout } from '../../store/slices/authSlice'; import { cn } from '../../utils/cn'; +import toast from 'react-hot-toast'; const navigation = [ { name: 'Dashboard', href: '/', icon: Home }, @@ -58,6 +60,7 @@ const Sidebar: React.FC = () => { const handleLogout = () => { dispatch(logout()); + toast.success('Logged out successfully'); }; return ( @@ -72,9 +75,14 @@ const Sidebar: React.FC = () => {
- - Channel Partners - + + Cloudtopiaa + )} @@ -83,16 +112,16 @@ const ResellerSidebar: React.FC = () => { {/* User Profile */} {!collapsed && ( -
+
-
+
-

{user?.name || 'John Reseller'}

-

{user?.company || 'Tech Solutions Inc'}

+

{user ? `${user.firstName} ${user.lastName}` : 'John Reseller'}

+

{user?.email || 'reseller@example.com'}

-
@@ -100,44 +129,48 @@ const ResellerSidebar: React.FC = () => { )} {/* Navigation */} -