Changeset 20d16ca for frontend/src
- Timestamp:
- 08/30/26 00:27:08 (2 weeks ago)
- Branches:
- master
- Children:
- e1f74f6
- Parents:
- b63d7b5
- Location:
- frontend/src
- Files:
-
- 3 edited
-
App.js (modified) (4 diffs)
-
components/Navbar.js (modified) (3 diffs)
-
pages/Login.js (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
-
frontend/src/App.js
rb63d7b5 r20d16ca 1 1 import React, { useEffect, useState } from 'react'; 2 import { BrowserRouter as Router, Routes, Route , Navigate} from 'react-router-dom';2 import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; 3 3 import Navbar from './components/Navbar'; 4 import ProtectedRoute from './components/ProtectedRoute'; 5 import Login from './pages/Login'; 4 6 import Dashboard from './pages/Dashboard'; 5 7 import PatientList from './pages/patients/PatientList'; … … 27 29 function App() { 28 30 const [user, setUser] = useState(null); 31 const [refresh, setRefresh] = useState(0); 29 32 30 33 useEffect(() => { 31 const userStr = localStorage.getItem('user'); 32 if (userStr) { 33 setUser(JSON.parse(userStr)); 34 } 35 }, []); 34 const loadUser = () => { 35 const userStr = localStorage.getItem('user'); 36 if (userStr) { 37 try { 38 setUser(JSON.parse(userStr)); 39 } catch (err) { 40 console.error('Failed to parse user:', err); 41 setUser(null); 42 } 43 } else { 44 setUser(null); 45 } 46 }; 47 48 loadUser(); 49 50 // Listen for storage changes (other tabs/windows) 51 window.addEventListener('storage', loadUser); 52 53 // Create a custom event listener for same-tab changes 54 const handleStorageChange = (e) => { 55 console.log('Storage change detected:', e.detail); 56 loadUser(); 57 }; 58 window.addEventListener('userStorageChange', handleStorageChange); 59 60 return () => { 61 window.removeEventListener('storage', loadUser); 62 window.removeEventListener('userStorageChange', handleStorageChange); 63 }; 64 }, [refresh]); 36 65 37 66 return ( … … 77 106 <Route path="/medical-reports" element={<ProtectedRoute><MedicalReportList /></ProtectedRoute>} /> 78 107 79 {/* Referral Routes */}80 <Route path="/referrals" element={<ProtectedRoute><ReferralList /></ProtectedRoute>} />81 82 108 {/* Lab Test Routes */} 83 109 <Route path="/lab-tests" element={<ProtectedRoute><LabTestList /></ProtectedRoute>} /> … … 92 118 <Route path="/billing/:id" element={<ProtectedRoute><BillingDetail /></ProtectedRoute>} /> 93 119 94 120 {/* Referral Routes */} 121 <Route path="/referrals" element={<ProtectedRoute><ReferralList /></ProtectedRoute>} /> 95 122 </Routes> 96 123 </main> -
frontend/src/components/Navbar.js
rb63d7b5 r20d16ca 9 9 10 10 useEffect(() => { 11 const userStr = localStorage.getItem('user'); 12 if (userStr) { 13 setUser(JSON.parse(userStr)); 14 } 11 const loadUser = () => { 12 const userStr = localStorage.getItem('user'); 13 if (userStr) { 14 setUser(JSON.parse(userStr)); 15 } else { 16 setUser(null); 17 } 18 }; 19 20 loadUser(); 21 22 window.addEventListener('storage', loadUser); 23 return () => window.removeEventListener('storage', loadUser); 15 24 }, []); 16 25 … … 19 28 localStorage.removeItem('user'); 20 29 setUser(null); 30 31 32 window.dispatchEvent(new CustomEvent('userStorageChange', { 33 detail: { userData: null } 34 })); 35 21 36 navigate('/login'); 22 37 }; … … 30 45 31 46 return ( 32 <nav className="navbar">33 <div className="navbar-brand">34 <img src="/logo.png" alt="Medora Logo" style={{ height: '40px', width: 'auto' }} />35 </div>47 <nav className="navbar"> 48 <div className="navbar-brand"> 49 <img src="/logo.png" alt="Medora Logo" style={{ height: '40px', width: 'auto' }} /> 50 </div> 36 51 37 <div className="navbar-links"> 38 <Link to="/" className={`navbar-link ${isActive('/') ? 'active' : ''}`}> 39 Overview 40 </Link> 41 {user?.role !== 'PATIENT' && user?.role !== 'LAB_TECHNICIAN' && user?.role !== 'BILLING_ADMIN' && ( 42 <Link to="/patients" className={`navbar-link ${isActive('/patients') ? 'active' : ''}`}> 43 Patients 52 <div className="navbar-links"> 53 <Link to="/" className={`navbar-link ${isActive('/') ? 'active' : ''}`}> 54 Overview 44 55 </Link> 45 )} 46 <Link to="/doctors" className={`navbar-link ${isActive('/doctors') ? 'active' : ''}`}> 47 Doctors 48 </Link> 49 <Link to="/departments" className={`navbar-link ${isActive('/departments') ? 'active' : ''}`}> 50 Departments 51 </Link> 52 {user?.role !== 'LAB_TECHNICIAN' && user?.role !== 'BILLING_ADMIN' && ( 53 <> 54 <Link to="/appointments" className={`navbar-link ${isActive('/appointments') ? 'active' : ''}`}> 55 Appointments 56 </Link> 57 {user?.role === 'PATIENT' && ( 56 {user?.role !== 'PATIENT' && user?.role !== 'LAB_TECHNICIAN' && user?.role !== 'BILLING_ADMIN' && ( 57 <Link to="/patients" className={`navbar-link ${isActive('/patients') ? 'active' : ''}`}> 58 Patients 59 </Link> 60 )} 61 <Link to="/doctors" className={`navbar-link ${isActive('/doctors') ? 'active' : ''}`}> 62 Doctors 63 </Link> 64 <Link to="/departments" className={`navbar-link ${isActive('/departments') ? 'active' : ''}`}> 65 Departments 66 </Link> 67 {user?.role !== 'LAB_TECHNICIAN' && user?.role !== 'BILLING_ADMIN' && ( 58 68 <> 59 <Link to="/ medical-records" className={`navbar-link ${isActive('/medical-records') ? 'active' : ''}`}>60 Medical Records69 <Link to="/appointments" className={`navbar-link ${isActive('/appointments') ? 'active' : ''}`}> 70 Appointments 61 71 </Link> 62 <Link to="/billing" className={`navbar-link ${isActive('/billing') ? 'active' : ''}`}> 63 Billing 64 </Link> 72 {user?.role === 'PATIENT' && ( 73 <> 74 <Link to="/medical-records" className={`navbar-link ${isActive('/medical-records') ? 'active' : ''}`}> 75 Medical Records 76 </Link> 77 <Link to="/billing" className={`navbar-link ${isActive('/billing') ? 'active' : ''}`}> 78 Billing 79 </Link> 80 </> 81 )} 65 82 </> 66 )} 67 </> 68 )} 69 {(user?.role === 'ADMIN' || user?.role === 'LAB_TECHNICIAN' || user?.role === 'DOCTOR') && ( 70 <Link to="/lab-tests" className={`navbar-link ${isActive('/lab-tests') ? 'active' : ''}`}> 71 Lab Tests 72 </Link> 73 )} 74 {!user?.role || user?.role === 'ADMIN' || user?.role === 'DOCTOR' ? ( 75 <> 76 {user?.role !== 'LAB_TECHNICIAN' && user?.role !== 'BILLING_ADMIN' && ( 77 <Link to="/medical-records" className={`navbar-link ${isActive('/medical-records') ? 'active' : ''}`}> 78 Medical Records 83 )} 84 {(user?.role === 'ADMIN' || user?.role === 'LAB_TECHNICIAN' || user?.role === 'DOCTOR') && ( 85 <Link to="/lab-tests" className={`navbar-link ${isActive('/lab-tests') ? 'active' : ''}`}> 86 Lab Tests 79 87 </Link> 80 )}81 {user?.role !== 'PATIENT' && user?.role !== 'LAB_TECHNICIAN' && user?.role !== 'BILLING_ADMIN' &&(88 )} 89 {!user?.role || user?.role === 'ADMIN' || user?.role === 'DOCTOR' ? ( 82 90 <> 83 <Link to="/medical-reports" className={`navbar-link ${isActive('/medical-reports') ? 'active' : ''}`}> 84 Medical Reports 85 </Link> 86 <Link to="/procedures" className={`navbar-link ${isActive('/procedures') ? 'active' : ''}`}> 87 Procedures 88 </Link> 89 <Link to="/referrals" className={`navbar-link ${isActive('/referrals') ? 'active' : ''}`}> 90 Referrals 91 </Link> 91 {user?.role !== 'LAB_TECHNICIAN' && user?.role !== 'BILLING_ADMIN' && ( 92 <Link to="/medical-records" className={`navbar-link ${isActive('/medical-records') ? 'active' : ''}`}> 93 Medical Records 94 </Link> 95 )} 96 {user?.role !== 'PATIENT' && user?.role !== 'LAB_TECHNICIAN' && user?.role !== 'BILLING_ADMIN' && ( 97 <> 98 <Link to="/medical-reports" className={`navbar-link ${isActive('/medical-reports') ? 'active' : ''}`}> 99 Medical Reports 100 </Link> 101 <Link to="/procedures" className={`navbar-link ${isActive('/procedures') ? 'active' : ''}`}> 102 Procedures 103 </Link> 104 <Link to="/referrals" className={`navbar-link ${isActive('/referrals') ? 'active' : ''}`}> 105 Referrals 106 </Link> 107 </> 108 )} 92 109 </> 93 )} 94 </> 95 ) : null} 96 {(user?.role === 'ADMIN' || user?.role === 'BILLING_ADMIN') && ( 97 <Link to="/billing" className={`navbar-link ${isActive('/billing') ? 'active' : ''}`}> 98 Billing 99 </Link> 100 )} 101 </div> 110 ) : null} 111 {(user?.role === 'ADMIN' || user?.role === 'BILLING_ADMIN') && ( 112 <Link to="/billing" className={`navbar-link ${isActive('/billing') ? 'active' : ''}`}> 113 Billing 114 </Link> 115 )} 116 </div> 102 117 103 <div className="navbar-right"> 104 {user && ( 105 <div className="navbar-avatar" style={{ position: 'relative' }}> 106 <div className="navbar-avatar-circle">{getInitials()}</div> 107 <div className="navbar-avatar-name">{user.firstName}</div> 108 <button 109 onClick={() => setShowMenu(!showMenu)} 110 style={{ 111 background: 'none', 112 border: 'none', 113 cursor: 'pointer', 114 marginLeft: '4px', 115 color: 'var(--color-neutral-600)', 116 fontSize: '12px' 117 }} 118 > 119 ▼ 120 </button> 121 {showMenu && ( 122 <div style={{ 123 position: 'absolute', 124 top: '100%', 125 right: 0, 126 background: 'white', 127 border: '1px solid var(--color-neutral-400)', 128 borderRadius: '6px', 129 minWidth: '150px', 130 marginTop: '4px', 131 boxShadow: '0 4px 6px rgba(0,0,0,0.1)', 132 zIndex: 1000 133 }}> 134 <div style={{ 135 padding: '8px 0', 136 fontSize: '11px', 137 color: 'var(--color-neutral-600)', 138 borderBottom: '1px solid var(--color-neutral-400)', 139 paddingLeft: '12px', 140 paddingRight: '12px', 141 paddingTop: '8px', 142 paddingBottom: '8px' 143 }}> 144 {user.role} 145 </div> 118 <div className="navbar-right"> 119 {user && ( 120 <div className="navbar-avatar" style={{ position: 'relative' }}> 121 <div className="navbar-avatar-circle">{getInitials()}</div> 122 <div className="navbar-avatar-name">{user.firstName}</div> 146 123 <button 147 onClick={handleLogout} 148 style={{ 149 width: '100%', 150 padding: '8px 12px', 151 background: 'none', 152 border: 'none', 153 textAlign: 'left', 154 cursor: 'pointer', 155 fontSize: '12px', 156 color: 'var(--color-status-red)', 157 transition: 'background 0.2s' 158 }} 159 onMouseEnter={(e) => e.target.style.background = 'var(--color-neutral-200)'} 160 onMouseLeave={(e) => e.target.style.background = 'none'} 124 onClick={() => setShowMenu(!showMenu)} 125 style={{ 126 background: 'none', 127 border: 'none', 128 cursor: 'pointer', 129 marginLeft: '4px', 130 color: 'var(--color-neutral-600)', 131 fontSize: '12px' 132 }} 161 133 > 162 Logout134 ▼ 163 135 </button> 136 {showMenu && ( 137 <div style={{ 138 position: 'absolute', 139 top: '100%', 140 right: 0, 141 background: 'white', 142 border: '1px solid var(--color-neutral-400)', 143 borderRadius: '6px', 144 minWidth: '150px', 145 marginTop: '4px', 146 boxShadow: '0 4px 6px rgba(0,0,0,0.1)', 147 zIndex: 1000 148 }}> 149 <div style={{ 150 padding: '8px 0', 151 fontSize: '11px', 152 color: 'var(--color-neutral-600)', 153 borderBottom: '1px solid var(--color-neutral-400)', 154 paddingLeft: '12px', 155 paddingRight: '12px', 156 paddingTop: '8px', 157 paddingBottom: '8px' 158 }}> 159 {user.role} 160 </div> 161 <button 162 onClick={handleLogout} 163 style={{ 164 width: '100%', 165 padding: '8px 12px', 166 background: 'none', 167 border: 'none', 168 textAlign: 'left', 169 cursor: 'pointer', 170 fontSize: '12px', 171 color: 'var(--color-status-red)', 172 transition: 'background 0.2s' 173 }} 174 onMouseEnter={(e) => e.target.style.background = 'var(--color-neutral-200)'} 175 onMouseLeave={(e) => e.target.style.background = 'none'} 176 > 177 Logout 178 </button> 179 </div> 180 )} 164 181 </div> 165 )} 166 </div> 167 )} 168 </div> 169 </nav> 182 )} 183 </div> 184 </nav> 170 185 ); 171 186 } -
frontend/src/pages/Login.js
rb63d7b5 r20d16ca 47 47 48 48 localStorage.setItem('token', data.token); 49 localStorage.setItem('user', JSON.stringify({49 const userData = { 50 50 userId: data.userId, 51 51 patientId: data.patientId, … … 55 55 firstName: data.firstName, 56 56 lastName: data.lastName, 57 }; 58 localStorage.setItem('user', JSON.stringify(userData)); 59 60 // Dispatch custom event to notify components on the same tab 61 window.dispatchEvent(new CustomEvent('userStorageChange', { 62 detail: { userData } 57 63 })); 58 64 … … 60 66 setTimeout(() => { 61 67 navigate('/'); 62 }, 1000);68 }, 500); 63 69 } catch (err) { 64 70 setError(err.message || 'Login failed'); … … 69 75 70 76 return ( 71 <div className="min-h-screen bg-blue-200 flex items-center justify-center p-4"> 72 <div className="bg-white rounded-lg shadow-2xl p-8 w-full max-w-md"> 73 <div className="text-center mb-8"> 74 <h1 className="text-5xl font-bold text-purple-600 mb-2">Medora</h1> 75 <p className="text-gray-600">Hospital Management System</p> 76 </div> 77 78 {error && <ErrorAlert message={error} onClose={() => setError(null)} />} 79 {success && <SuccessAlert message={success} onClose={() => setSuccess(null)} />} 80 81 <form onSubmit={handleLogin} className="space-y-6"> 82 <div> 83 <label className="block text-sm font-normal text-gray-700 mb-2"> 84 Username (EMBG for Patients) 85 </label> 86 <input 87 type="text" 88 value={username} 89 onChange={(e) => setUsername(e.target.value)} 90 placeholder="Enter your username or EMBG" 91 className="w-full px-4 py-3 border-2 border-gray-300 rounded-lg focus:outline-none focus:border-purple-500 transition" 92 disabled={loading} 93 /> 77 <div className="min-h-screen bg-blue-200 flex items-center justify-center p-4"> 78 <div className="bg-white rounded-lg shadow-2xl p-8 w-full max-w-md"> 79 <div className="text-center mb-8"> 80 <h1 className="text-5xl font-bold text-purple-600 mb-2">Medora</h1> 81 <p className="text-gray-600">Hospital Management System</p> 94 82 </div> 95 83 96 <div> 97 <label className="block text-sm font-normal text-gray-700 mb-2"> 98 Password 99 </label> 100 <input 101 type="password" 102 value={password} 103 onChange={(e) => setPassword(e.target.value)} 104 placeholder="Enter your password" 105 className="w-full px-4 py-3 border-2 border-gray-300 rounded-lg focus:outline-none focus:border-purple-500 transition" 106 disabled={loading} 107 /> 84 {error && <ErrorAlert message={error} onClose={() => setError(null)} />} 85 {success && <SuccessAlert message={success} onClose={() => setSuccess(null)} />} 86 87 <form onSubmit={handleLogin} className="space-y-6"> 88 <div> 89 <label className="block text-sm font-normal text-gray-700 mb-2"> 90 Username (EMBG for Patients) 91 </label> 92 <input 93 type="text" 94 value={username} 95 onChange={(e) => setUsername(e.target.value)} 96 placeholder="Enter your username or EMBG" 97 className="w-full px-4 py-3 border-2 border-gray-300 rounded-lg focus:outline-none focus:border-purple-500 transition" 98 disabled={loading} 99 /> 100 </div> 101 102 <div> 103 <label className="block text-sm font-normal text-gray-700 mb-2"> 104 Password 105 </label> 106 <input 107 type="password" 108 value={password} 109 onChange={(e) => setPassword(e.target.value)} 110 placeholder="Enter your password" 111 className="w-full px-4 py-3 border-2 border-gray-300 rounded-lg focus:outline-none focus:border-purple-500 transition" 112 disabled={loading} 113 /> 114 </div> 115 116 <button 117 type="submit" 118 disabled={loading} 119 className="w-full bg-purple-600 text-white font-bold py-3 rounded-lg hover:bg-purple-700 transition disabled:opacity-50 disabled:cursor-not-allowed" 120 > 121 {loading ? 'Logging in...' : 'Login'} 122 </button> 123 </form> 124 125 <div className="mt-8 p-4 bg-purple-50 rounded-lg border border-purple-200"> 126 <p className="text-sm text-gray-700 font-normal mb-2">Demo Credentials:</p> 127 <p className="text-sm text-gray-600 mb-2"> 128 <strong>Admin:</strong> username: admin | password: admin123 129 </p> 130 <p className="text-sm text-gray-600 mb-2"> 131 <strong>Doctor:</strong> username: ivan.stojanov@medora.com | password: doctor123 132 </p> 133 <p className="text-sm text-gray-600"> 134 <strong>Patient:</strong> username: [EMBG] | password: password123 135 </p> 108 136 </div> 109 110 <button111 type="submit"112 disabled={loading}113 className="w-full bg-purple-600 text-white font-bold py-3 rounded-lg hover:bg-purple-700 transition disabled:opacity-50 disabled:cursor-not-allowed"114 >115 {loading ? 'Logging in...' : 'Login'}116 </button>117 </form>118 119 <div className="mt-8 p-4 bg-purple-50 rounded-lg border border-purple-200">120 <p className="text-sm text-gray-700 font-normal mb-2">Demo Credentials:</p>121 <p className="text-sm text-gray-600 mb-2">122 <strong>Admin:</strong> username: admin | password: admin123123 </p>124 <p className="text-sm text-gray-600 mb-2">125 <strong>Doctor:</strong> username: ivan.stojanov@medora.com | password: doctor123126 </p>127 <p className="text-sm text-gray-600">128 <strong>Patient:</strong> username: [EMBG] | password: password123129 </p>130 137 </div> 131 138 </div> 132 </div>133 139 ); 134 140 }
Note:
See TracChangeset
for help on using the changeset viewer.
