Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link rel="icon" href="/logo.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>medsync-react</title>
</head>
Expand Down
20 changes: 10 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
},
"dependencies": {
"daisyui": "^5.1.30",
"lucide-react": "^0.546.0",
"react": "^19.1.1",
"react-dom": "^19.1.1"
},
Expand Down
Binary file added public/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion public/vite.svg

This file was deleted.

147 changes: 130 additions & 17 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@ import Reports from './components/Reports'
import Administration from './components/Administration'
import Sidebar from './components/Sidebar'
import { api, API_ENDPOINTS } from './config/api'
import Home from './components/Home'

function App() {
const STORAGE_KEY = 'medsync_user'
const [isLoggedIn, setIsLoggedIn] = useState(false)
const [isSidebarOpen, setIsSidebarOpen] = useState(false)
const [showLogin, setShowLogin] = useState(false)
const [user, setUser] = useState(null)
const [activeSection, setActiveSection] = useState('dashboard')
const [loading, setLoading] = useState(true)
Expand All @@ -22,7 +26,40 @@ function App() {
checkAuthStatus()
}, [])

const getStoredUser = () => {
if (typeof window === 'undefined') return null
const raw = window.localStorage.getItem(STORAGE_KEY)
if (!raw) return null

try {
return JSON.parse(raw)
} catch (error) {
window.localStorage.removeItem(STORAGE_KEY)
console.error('Failed to parse stored user data:', error)
return null
}
}

const persistUser = (userData) => {
if (typeof window === 'undefined') return
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(userData))
}

const clearStoredUser = () => {
if (typeof window === 'undefined') return
window.localStorage.removeItem(STORAGE_KEY)
}

const checkAuthStatus = async () => {
const storedUser = getStoredUser()

if (storedUser) {
setUser(storedUser)
setIsLoggedIn(true)
setActiveSection('dashboard')
setShowLogin(false)
}

try {
// Add timeout to prevent infinite loading if backend is down
const controller = new AbortController()
Expand All @@ -34,6 +71,14 @@ function App() {
if (response.isAuthenticated && response.user) {
setUser(response.user)
setIsLoggedIn(true)
setActiveSection('dashboard')
persistUser(response.user)
setShowLogin(false)
} else {
clearStoredUser()
setIsLoggedIn(false)
setUser(null)
setShowLogin(false)
}
} catch (err) {
console.error('Auth check error:', err)
Expand All @@ -44,8 +89,12 @@ function App() {
}

const handleLogin = (userData) => {
persistUser(userData)
setUser(userData)
setIsLoggedIn(true)
setActiveSection('dashboard')
setShowLogin(false)
setIsSidebarOpen(false)
}

const handleLogout = async () => {
Expand All @@ -56,10 +105,18 @@ function App() {
} finally {
setIsLoggedIn(false)
setUser(null)
clearStoredUser()
setActiveSection('dashboard')
setShowLogin(false)
setIsSidebarOpen(false)
}
}

const handleNavigate = (section) => {
setActiveSection(section)
setIsSidebarOpen(false)
}

if (loading) {
return (
<div className="flex items-center justify-center min-h-screen bg-white">
Expand All @@ -69,7 +126,29 @@ function App() {
}

if (!isLoggedIn) {
return <Login onLogin={handleLogin} />
if (showLogin) {
return <Login onLogin={handleLogin} onBack={() => setShowLogin(false)} />
}

return (
<Home
user={null}
isLoggedIn={false}
onGoToDashboard={() => setShowLogin(true)}
onLoginRequest={() => setShowLogin(true)}
/>
)
}

if (activeSection === 'home') {
return (
<Home
user={user}
isLoggedIn
onGoToDashboard={() => handleNavigate('dashboard')}
onLogout={handleLogout}
/>
)
}

const renderSection = () => {
Expand All @@ -96,38 +175,72 @@ function App() {
}

return (
<div className="h-screen grid grid-cols-[240px_1fr]">
<div className="min-h-screen bg-background md:grid md:grid-cols-[240px_1fr]">
<Sidebar
activeSection={activeSection}
onNavigate={setActiveSection}
onNavigate={handleNavigate}
userRole={user?.role || 'receptionist'}
onLogout={handleLogout}
className="hidden md:flex md:h-full"
/>

{/* Main Content */}
<div className="overflow-y-auto">
<div className="flex min-h-screen flex-col">
{/* Top Bar */}
<div className="sticky top-0 z-100 px-8 py-6 border-b border-gray-light bg-white flex justify-between items-center">
<div className="flex-1 max-w-[400px]">
<input
type="text"
placeholder="Search patients, appointments, invoices..."
className="w-full px-4 py-2.5 border border-gray-light text-[13px] focus:outline-none focus:ring-0"
/>
<div className="sticky top-0 z-100 flex items-center justify-between gap-4 border-b border-gray-light bg-white px-4 py-4 md:px-8 md:py-6">
<div className="flex items-center gap-3">
<button
type="button"
className="rounded border border-gray-light px-3 py-2 text-sm font-semibold uppercase tracking-widest text-gray-700 transition hover:bg-gray-bg md:hidden"
onClick={() => setIsSidebarOpen(true)}
>
Menu
</button>
<div className="hidden sm:block">
<h2 className="text-sm font-semibold uppercase tracking-widest text-gray-mid">
{activeSection.charAt(0).toUpperCase() + activeSection.slice(1)}
</h2>
</div>
</div>
<div className="flex gap-4 items-center">
<div className="w-10 h-10 border border-gray-light flex items-center justify-center cursor-pointer relative">

<div className="flex flex-1 justify-end gap-3 md:gap-4">
<div className="hidden flex-1 max-w-[320px] sm:block">
<input
type="text"
placeholder="Search patients, appointments, invoices..."
className="w-full rounded border border-gray-light px-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary/40"
/>
</div>
<div className="flex h-10 w-10 items-center justify-center border border-gray-light">
<span>◆</span>
<div className="absolute -top-1 -right-1 bg-crimson text-white w-4 h-4 rounded-full text-[9px] flex items-center justify-center font-semibold">
5
</div>
</div>
</div>
</div>

{/* Content Inner */}
{renderSection()}
<div className="flex-1 overflow-y-auto px-4 py-6 md:px-8 md:py-8">
{renderSection()}
</div>
</div>

{/* Mobile Sidebar */}
{isSidebarOpen && (
<div className="fixed inset-0 z-[120] flex md:hidden">
<div
className="flex-1 bg-black/40"
onClick={() => setIsSidebarOpen(false)}
role="presentation"
/>
<Sidebar
activeSection={activeSection}
onNavigate={handleNavigate}
userRole={user?.role || 'receptionist'}
onLogout={handleLogout}
className="h-full w-72 max-w-[80%] shadow-2xl"
onClose={() => setIsSidebarOpen(false)}
/>
</div>
)}
</div>
)
}
Expand Down
Binary file added src/assets/doctor-hero.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/doctor-patient.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/medical-professional.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion src/assets/react.svg

This file was deleted.

Loading