dasyui and telwind
This commit is contained in:
parent
02f98cb460
commit
d33c603e6c
File diff suppressed because it is too large
Load Diff
|
|
@ -18,10 +18,14 @@
|
||||||
"test:unit": "vitest src"
|
"test:unit": "vitest src"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@tailwindcss/vite": "^4.1.16",
|
||||||
|
"lucide-react": "^0.552.0",
|
||||||
"os-utils": "^0.0.14",
|
"os-utils": "^0.0.14",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"recharts": "^2.12.7"
|
"react-router-dom": "^7.9.5",
|
||||||
|
"recharts": "^2.12.7",
|
||||||
|
"tailwindcss": "^4.1.16"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.47.2",
|
"@playwright/test": "^1.47.2",
|
||||||
|
|
@ -31,6 +35,7 @@
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
"@vitejs/plugin-react": "^4.3.1",
|
"@vitejs/plugin-react": "^4.3.1",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
|
"daisyui": "^5.4.3",
|
||||||
"electron": "^32.1.2",
|
"electron": "^32.1.2",
|
||||||
"electron-builder": "^25.0.5",
|
"electron-builder": "^25.0.5",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
|
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
header {
|
|
||||||
width: 100%;
|
|
||||||
text-align: left;
|
|
||||||
padding: 0.5rem;
|
|
||||||
box-sizing: border-box;
|
|
||||||
background-color: #181818;
|
|
||||||
-webkit-app-region: drag;
|
|
||||||
}
|
|
||||||
|
|
||||||
header button {
|
|
||||||
all: unset;
|
|
||||||
border-radius: 50%;
|
|
||||||
width: 1rem;
|
|
||||||
height: 1rem;
|
|
||||||
margin: 0.25rem;
|
|
||||||
-webkit-app-region: no-drag;
|
|
||||||
}
|
|
||||||
|
|
||||||
#close {
|
|
||||||
background-color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
#minimize {
|
|
||||||
background-color: yellow;
|
|
||||||
}
|
|
||||||
|
|
||||||
#maximize {
|
|
||||||
background-color: lime;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 16rem auto;
|
|
||||||
gap: 1rem;
|
|
||||||
align-items: center;
|
|
||||||
margin: 1rem;
|
|
||||||
max-width: 900px;
|
|
||||||
margin: auto;
|
|
||||||
margin-top: 4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mainGrid {
|
|
||||||
height: 10rem;
|
|
||||||
padding: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectOption {
|
|
||||||
display: block;
|
|
||||||
padding: 0.5rem;
|
|
||||||
width: 100%;
|
|
||||||
background-color: #2c2c2c;
|
|
||||||
margin: 0.5rem 0;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectOption:hover {
|
|
||||||
background-color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectOptionTitle {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectOptionTitle :first-child {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectOptionTitle :nth-child(2) {
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectOptionChart {
|
|
||||||
height: 2rem;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectOptionChart * {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +1,57 @@
|
||||||
import './App.css';
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import { BrowserRouter, Route, Routes, Navigate } from 'react-router-dom'
|
||||||
|
import Login from './pages/Login'
|
||||||
|
import Dashboard from './pages/Dashboard'
|
||||||
|
import Projects from './pages/Projects/Projects'
|
||||||
|
import DashboardLayout from './layout/DashboardLayout'
|
||||||
|
import ProtectedRoute from './routes/ProtectedRoute'
|
||||||
|
import PublicRoute from './routes/PublicRoute'
|
||||||
|
import { ThemeToggle } from './helper/ThemeToggle'
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
const theme = ThemeToggle()
|
||||||
|
const [user, setUser] = useState(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.documentElement.setAttribute('data-theme', theme)
|
||||||
|
}, [theme])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const storedUser = localStorage.getItem('user')
|
||||||
|
if (storedUser) setUser(JSON.parse(storedUser))
|
||||||
|
}, [])
|
||||||
|
|
||||||
function App() {
|
|
||||||
return (
|
return (
|
||||||
<>
|
<BrowserRouter>
|
||||||
<h1>Tracker</h1>
|
<Routes>
|
||||||
</>
|
{/* Public Route */}
|
||||||
|
<Route
|
||||||
|
path="/login"
|
||||||
|
element={
|
||||||
|
<PublicRoute user={user}>
|
||||||
|
<Login setUser={setUser} />
|
||||||
|
</PublicRoute>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Protected Routes */}
|
||||||
|
<Route
|
||||||
|
path="/dashboard"
|
||||||
|
element={
|
||||||
|
<ProtectedRoute user={user}>
|
||||||
|
<DashboardLayout user={user} setUser={setUser} />
|
||||||
|
</ProtectedRoute>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Route index element={<Dashboard />} />
|
||||||
|
<Route path="projects" element={<Projects />} />
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
{/* Default Redirect */}
|
||||||
|
<Route path="*" element={<Navigate to={user ? '/dashboard' : '/login'} />} />
|
||||||
|
</Routes>
|
||||||
|
</BrowserRouter>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default App
|
||||||
export default App;
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
// src/hooks/ThemeToggle.js
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
|
export const ThemeToggle = () => {
|
||||||
|
const [theme, setTheme] = useState("light");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const darkQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
||||||
|
|
||||||
|
// Initial check
|
||||||
|
setTheme(darkQuery.matches ? "dark" : "light");
|
||||||
|
|
||||||
|
// Listen for changes
|
||||||
|
const handleChange = (e) => {
|
||||||
|
setTheme(e.matches ? "dark" : "light");
|
||||||
|
};
|
||||||
|
|
||||||
|
darkQuery.addEventListener("change", handleChange);
|
||||||
|
|
||||||
|
return () => darkQuery.removeEventListener("change", handleChange);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return theme;
|
||||||
|
};
|
||||||
|
|
@ -1,24 +1,23 @@
|
||||||
:root {
|
@import "tailwindcss";
|
||||||
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
|
@plugin "daisyui";
|
||||||
font-size: 16px;
|
|
||||||
line-height: 24px;
|
|
||||||
font-weight: 400;
|
|
||||||
|
|
||||||
color-scheme: light dark;
|
|
||||||
color: rgba(255, 255, 255, 0.87);
|
|
||||||
background-color: #242424;
|
|
||||||
|
|
||||||
font-synthesis: none;
|
.main-con::-webkit-scrollbar {
|
||||||
text-rendering: optimizeLegibility;
|
display: none;
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
-webkit-text-size-adjust: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
@layer utilities {
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
/* Hide scrollbar for Chrome, Safari and Opera */
|
||||||
all: unset;
|
.no-scrollbar::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide scrollbar for IE, Edge and Firefox */
|
||||||
|
.no-scrollbar {
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
/* IE and Edge */
|
||||||
|
scrollbar-width: none;
|
||||||
|
/* Firefox */
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,167 @@
|
||||||
|
import React, { useState, useEffect } from 'react'
|
||||||
|
import { Outlet, NavLink, useNavigate, useLocation } from 'react-router-dom'
|
||||||
|
import { Bell, Menu, LogOut, User, Settings, Home, FolderKanban, X } from 'lucide-react'
|
||||||
|
|
||||||
|
const DashboardLayout = ({ user, setUser }) => {
|
||||||
|
const [isSidebarOpen, setSidebarOpen] = useState(true)
|
||||||
|
const [isMobile, setIsMobile] = useState(window.innerWidth < 768)
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const location = useLocation()
|
||||||
|
|
||||||
|
// ✅ Handle responsive behavior properly
|
||||||
|
useEffect(() => {
|
||||||
|
const handleResize = () => {
|
||||||
|
const mobile = window.innerWidth < 768
|
||||||
|
setIsMobile(mobile)
|
||||||
|
setSidebarOpen(!mobile) // open on desktop, closed on mobile
|
||||||
|
}
|
||||||
|
|
||||||
|
handleResize()
|
||||||
|
window.addEventListener('resize', handleResize)
|
||||||
|
return () => window.removeEventListener('resize', handleResize)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleLogout = () => {
|
||||||
|
localStorage.removeItem('user')
|
||||||
|
setUser(null)
|
||||||
|
navigate('/login')
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleSidebar = () => setSidebarOpen(!isSidebarOpen)
|
||||||
|
|
||||||
|
const closeSidebarOnMobile = () => {
|
||||||
|
if (isMobile) setSidebarOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const menuItems = [
|
||||||
|
{ label: 'Dashboard', path: '/dashboard', icon: <Home size={18} />, exact: true },
|
||||||
|
{ label: 'Projects', path: '/dashboard/projects', icon: <FolderKanban size={18} /> },
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex h-screen bg-base-200 overflow-hidden">
|
||||||
|
{/* Sidebar */}
|
||||||
|
<aside
|
||||||
|
className={`
|
||||||
|
fixed md:relative inset-y-0 left-0 z-40 transform py-1
|
||||||
|
bg-base-100 border-r border-base-300 transition-all duration-300 ease-in-out
|
||||||
|
${isMobile
|
||||||
|
? isSidebarOpen
|
||||||
|
? 'translate-x-0 w-64'
|
||||||
|
: '-translate-x-full w-64'
|
||||||
|
: isSidebarOpen
|
||||||
|
? 'w-64'
|
||||||
|
: 'w-20'}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{/* Header */}
|
||||||
|
<div className="p-4 text-xl font-bold flex justify-between items-center border-b border-base-300">
|
||||||
|
{isSidebarOpen ? (
|
||||||
|
<span className="whitespace-nowrap">VA Tracker</span>
|
||||||
|
) : (
|
||||||
|
<span className="text-primary font-extrabold">VA</span>
|
||||||
|
)}
|
||||||
|
{isMobile && (
|
||||||
|
<button
|
||||||
|
onClick={toggleSidebar}
|
||||||
|
className="btn btn-ghost btn-sm md:hidden"
|
||||||
|
aria-label="Close sidebar"
|
||||||
|
>
|
||||||
|
<X size={20} />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Menu */}
|
||||||
|
<ul className="menu px-2 py-4">
|
||||||
|
{menuItems.map((item) => (
|
||||||
|
<li key={item.label}>
|
||||||
|
<NavLink
|
||||||
|
to={item.path}
|
||||||
|
end={item.exact}
|
||||||
|
className={({ isActive }) =>
|
||||||
|
`flex items-center gap-3 my-1 font-medium transition-all duration-150 ${
|
||||||
|
isActive
|
||||||
|
? 'bg-primary text-primary-content rounded-lg'
|
||||||
|
: 'hover:bg-base-300 rounded-lg'
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
onClick={closeSidebarOnMobile}
|
||||||
|
>
|
||||||
|
<div className="min-w-[24px] flex justify-center">{item.icon}</div>
|
||||||
|
{isSidebarOpen && <span>{item.label}</span>}
|
||||||
|
</NavLink>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
{/* Overlay for mobile */}
|
||||||
|
{isMobile && isSidebarOpen && (
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 bg-black/20 backdrop-blur-[1px] z-30 md:hidden"
|
||||||
|
onClick={closeSidebarOnMobile}
|
||||||
|
></div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Main Content */}
|
||||||
|
<div
|
||||||
|
className={`flex flex-col flex-1 transition-all duration-300 ease-in-out bg-base-200 ${
|
||||||
|
!isMobile ? (isSidebarOpen ? 'md:ml-0' : 'md:ml-0') : ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{/* Navbar */}
|
||||||
|
<nav className="navbar bg-base-100 border-b border-base-300 p-3 sticky top-0 z-30 flex justify-between items-center">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<button
|
||||||
|
onClick={toggleSidebar}
|
||||||
|
className="btn btn-ghost btn-square"
|
||||||
|
aria-label="Toggle sidebar"
|
||||||
|
>
|
||||||
|
<Menu size={20} />
|
||||||
|
</button>
|
||||||
|
<span className="text-lg font-semibold ml-2 md:hidden">
|
||||||
|
{location.pathname.includes('projects') ? 'Projects' : 'Dashboard'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<button className="btn btn-ghost btn-circle">
|
||||||
|
<Bell size={20} />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className="dropdown dropdown-end">
|
||||||
|
<label tabIndex={0} className="btn btn-ghost btn-circle avatar">
|
||||||
|
<div className="w-10 rounded-full ring ring-primary ring-offset-base-100 ring-offset-2 flex justify-center items-center">
|
||||||
|
<User size={22} />
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<ul
|
||||||
|
tabIndex={0}
|
||||||
|
className="mt-3 p-2 shadow menu menu-sm dropdown-content bg-base-200 rounded-box w-52"
|
||||||
|
>
|
||||||
|
<li>
|
||||||
|
<a>
|
||||||
|
<Settings size={16} /> Settings
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a onClick={handleLogout} className="text-error">
|
||||||
|
<LogOut size={16} /> Logout
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{/* Page Content */}
|
||||||
|
<main className="flex-1 overflow-y-auto p-4 md:p-6 no-scrollbar">
|
||||||
|
<Outlet />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DashboardLayout
|
||||||
|
|
@ -0,0 +1,530 @@
|
||||||
|
import React, { useState, useEffect } from 'react'
|
||||||
|
import {
|
||||||
|
Menu,
|
||||||
|
X,
|
||||||
|
Clock,
|
||||||
|
Activity,
|
||||||
|
TrendingUp,
|
||||||
|
Calendar,
|
||||||
|
User,
|
||||||
|
LogOut,
|
||||||
|
Settings,
|
||||||
|
Bell,
|
||||||
|
Search,
|
||||||
|
Play,
|
||||||
|
Square,
|
||||||
|
ProjectorIcon,
|
||||||
|
Projector,
|
||||||
|
ToolCase,
|
||||||
|
History,
|
||||||
|
HelpCircle,
|
||||||
|
CalendarDays
|
||||||
|
} from 'lucide-react'
|
||||||
|
import { NavLink } from 'react-router-dom'
|
||||||
|
|
||||||
|
const projectsData = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
projectName: 'Employee Tracker',
|
||||||
|
clientName: 'TechCorp Pvt Ltd',
|
||||||
|
status: 'Active',
|
||||||
|
action: 'Start Tracking'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
projectName: 'Sales Dashboard',
|
||||||
|
clientName: 'BlueEdge Analytics',
|
||||||
|
status: 'Pending',
|
||||||
|
action: 'Start Tracking'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
projectName: 'Inventory System',
|
||||||
|
clientName: 'LogiFlow Inc',
|
||||||
|
status: 'Completed',
|
||||||
|
action: 'View Report'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
projectName: 'CRM Upgrade',
|
||||||
|
clientName: 'NextWave Solutions',
|
||||||
|
status: 'On Hold',
|
||||||
|
action: 'Resume'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
projectName: 'Mobile App Redesign',
|
||||||
|
clientName: 'PixelFusion',
|
||||||
|
status: 'Active',
|
||||||
|
action: 'Start Tracking'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
projectName: 'E-Commerce Backend',
|
||||||
|
clientName: 'ShopEasy',
|
||||||
|
status: 'Pending',
|
||||||
|
action: 'Start Tracking'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
projectName: 'Finance Tracker',
|
||||||
|
clientName: 'MoneyWise',
|
||||||
|
status: 'Active',
|
||||||
|
action: 'Start Tracking'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 8,
|
||||||
|
projectName: 'Helpdesk Portal',
|
||||||
|
clientName: 'ServePro',
|
||||||
|
status: 'Completed',
|
||||||
|
action: 'View Report'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 9,
|
||||||
|
projectName: 'Marketing Automation',
|
||||||
|
clientName: 'AdBoost',
|
||||||
|
status: 'Active',
|
||||||
|
action: 'Start Tracking'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 10,
|
||||||
|
projectName: 'Data Pipeline',
|
||||||
|
clientName: 'CloudWave',
|
||||||
|
status: 'On Hold',
|
||||||
|
action: 'Resume'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 11,
|
||||||
|
projectName: 'Support Chatbot',
|
||||||
|
clientName: 'SmartServe',
|
||||||
|
status: 'Active',
|
||||||
|
action: 'Start Tracking'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 12,
|
||||||
|
projectName: 'Payroll System',
|
||||||
|
clientName: 'PeopleFirst HR',
|
||||||
|
status: 'Pending',
|
||||||
|
action: 'Start Tracking'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 13,
|
||||||
|
projectName: 'IoT Dashboard',
|
||||||
|
clientName: 'SensorGrid',
|
||||||
|
status: 'Completed',
|
||||||
|
action: 'View Report'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 14,
|
||||||
|
projectName: 'Security Portal',
|
||||||
|
clientName: 'SafeNet',
|
||||||
|
status: 'Active',
|
||||||
|
action: 'Start Tracking'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 15,
|
||||||
|
projectName: 'Social Media Tool',
|
||||||
|
clientName: 'Buzzify',
|
||||||
|
status: 'Pending',
|
||||||
|
action: 'Start Tracking'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 16,
|
||||||
|
projectName: 'AI Model Trainer',
|
||||||
|
clientName: 'NeuroTech',
|
||||||
|
status: 'On Hold',
|
||||||
|
action: 'Resume'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 17,
|
||||||
|
projectName: 'Project Tracker',
|
||||||
|
clientName: 'TaskFlow',
|
||||||
|
status: 'Active',
|
||||||
|
action: 'Start Tracking'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 18,
|
||||||
|
projectName: 'Booking Portal',
|
||||||
|
clientName: 'GoTravel',
|
||||||
|
status: 'Completed',
|
||||||
|
action: 'View Report'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 19,
|
||||||
|
projectName: 'Time Logger',
|
||||||
|
clientName: 'Clockify Pro',
|
||||||
|
status: 'Active',
|
||||||
|
action: 'Start Tracking'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 20,
|
||||||
|
projectName: 'Analytics Engine',
|
||||||
|
clientName: 'DataStream',
|
||||||
|
status: 'Pending',
|
||||||
|
action: 'Start Tracking'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// Sidebar Component
|
||||||
|
|
||||||
|
|
||||||
|
// Navbar Component
|
||||||
|
const Navbar = ({ toggleSidebar, user }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* Navbar */}
|
||||||
|
<div className="navbar bg-base-100 px-4 sticky top-0 z-30 border-b border-base-300 py-4 ">
|
||||||
|
{/* Left side */}
|
||||||
|
<div className="flex-1 gap-2">
|
||||||
|
<button onClick={toggleSidebar} className="btn btn-ghost btn-square lg:hidden">
|
||||||
|
<Menu size={24} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right side */}
|
||||||
|
<div className="flex-none gap-2">
|
||||||
|
{/* User Dropdown */}
|
||||||
|
<div className="dropdown dropdown-end mx-3">
|
||||||
|
<label tabIndex={0} className="btn btn-ghost btn-circle avatar">
|
||||||
|
<div className="w-10 rounded-full ring ring-primary ring-offset-base-100 ring-offset-2 flex justify-center items-center">
|
||||||
|
<User size={22} />
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<ul
|
||||||
|
tabIndex={0}
|
||||||
|
className="mt-3 p-2 shadow-lg menu menu-sm dropdown-content bg-base-200 rounded-box w-64"
|
||||||
|
>
|
||||||
|
<li className="menu-title">
|
||||||
|
<div className="flex items-center gap-3 p-2">
|
||||||
|
<div className="avatar online">
|
||||||
|
<div className="w-12 rounded-full flex justify-center items-center">
|
||||||
|
<User size={25} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="font-semibold">{user.name}</p>
|
||||||
|
<p className="text-xs text-base-content/60">{user.email}</p>
|
||||||
|
<div className="badge badge-success badge-xs mt-1">Online</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<div className="divider my-1"></div>
|
||||||
|
<li>
|
||||||
|
<a>
|
||||||
|
<Settings size={16} /> Settings
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<div className="divider my-1"></div>
|
||||||
|
<li>
|
||||||
|
<a className="text-error">
|
||||||
|
<LogOut size={16} /> Logout
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Widget Component
|
||||||
|
const Widget = ({ title, value, subtitle, icon, trend, color = 'primary' }) => {
|
||||||
|
return (
|
||||||
|
<div className="card bg-base-100 shadow-md hover:shadow-2xl transition-shadow">
|
||||||
|
<div className="card-body">
|
||||||
|
<div className="flex items-start justify-between">
|
||||||
|
<div className="flex-1">
|
||||||
|
<h3 className="text-sm font-medium text-base-content/60">{title}</h3>
|
||||||
|
<p className="text-3xl font-bold mt-2">{value}</p>
|
||||||
|
</div>
|
||||||
|
<div className={`p-3 bg-${color}/10 rounded-lg`}>
|
||||||
|
<div className={`text-${color}`}>{icon}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Activity Timeline Component
|
||||||
|
const ActivityTimeline = () => {
|
||||||
|
const activities = [
|
||||||
|
{ time: '09:00 AM', status: 'online', label: 'Started working' },
|
||||||
|
{ time: '10:30 AM', status: 'productive', label: 'Deep work session' },
|
||||||
|
{ time: '12:00 PM', status: 'idle', label: 'Break time' },
|
||||||
|
{ time: '01:00 PM', status: 'online', label: 'Resumed work' },
|
||||||
|
{ time: '03:30 PM', status: 'productive', label: 'Meeting completed' }
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="card bg-base-100 shadow-xl">
|
||||||
|
<div className="card-body">
|
||||||
|
<h2 className="card-title">Today's Activity</h2>
|
||||||
|
<div className="space-y-4 mt-4">
|
||||||
|
{activities.map((activity, index) => (
|
||||||
|
<div key={index} className="flex items-start gap-4">
|
||||||
|
<div className="flex flex-col items-center">
|
||||||
|
<div
|
||||||
|
className={`w-3 h-3 rounded-full ${
|
||||||
|
activity.status === 'online'
|
||||||
|
? 'bg-success'
|
||||||
|
: activity.status === 'productive'
|
||||||
|
? 'bg-primary'
|
||||||
|
: 'bg-warning'
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
{index < activities.length - 1 && <div className="w-0.5 h-12 bg-base-300 my-1" />}
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<p className="text-sm font-medium">{activity.label}</p>
|
||||||
|
<p className="text-xs text-base-content/60">{activity.time}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Progress Component
|
||||||
|
const ProductivityProgress = () => {
|
||||||
|
return (
|
||||||
|
<div className="card bg-base-100 shadow-xl">
|
||||||
|
<div className="card-body">
|
||||||
|
<h2 className="card-title">Productivity Breakdown</h2>
|
||||||
|
<div className="space-y-4 mt-4">
|
||||||
|
<div>
|
||||||
|
<div className="flex justify-between text-sm mb-1">
|
||||||
|
<span>Productive Time</span>
|
||||||
|
<span className="font-semibold">75%</span>
|
||||||
|
</div>
|
||||||
|
<progress className="progress progress-success" value="75" max="100"></progress>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="flex justify-between text-sm mb-1">
|
||||||
|
<span>Idle Time</span>
|
||||||
|
<span className="font-semibold">15%</span>
|
||||||
|
</div>
|
||||||
|
<progress className="progress progress-warning" value="15" max="100"></progress>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="flex justify-between text-sm mb-1">
|
||||||
|
<span>Break Time</span>
|
||||||
|
<span className="font-semibold">10%</span>
|
||||||
|
</div>
|
||||||
|
<progress className="progress progress-info" value="10" max="100"></progress>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="stats stats-vertical shadow mt-4">
|
||||||
|
<div className="stat py-3">
|
||||||
|
<div className="stat-title text-xs">Efficiency Score</div>
|
||||||
|
<div className="stat-value text-2xl text-success">92%</div>
|
||||||
|
<div className="stat-desc">Above team average</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main Dashboard Component
|
||||||
|
const Dashboard = () => {
|
||||||
|
const [sidebarOpen, setSidebarOpen] = useState(false)
|
||||||
|
const [theme, setTheme] = useState('light')
|
||||||
|
const [currentTime, setCurrentTime] = useState(new Date())
|
||||||
|
|
||||||
|
const user = {
|
||||||
|
name: 'John Doe',
|
||||||
|
email: 'john@company.com',
|
||||||
|
avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=John',
|
||||||
|
status: 'online'
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const darkModeQuery = window.matchMedia('(prefers-color-scheme: dark)')
|
||||||
|
const systemTheme = darkModeQuery.matches ? 'dark' : 'light'
|
||||||
|
setTheme(systemTheme)
|
||||||
|
document.documentElement.setAttribute('data-theme', systemTheme)
|
||||||
|
|
||||||
|
const handleThemeChange = (e) => {
|
||||||
|
const newTheme = e.matches ? 'dark' : 'light'
|
||||||
|
setTheme(newTheme)
|
||||||
|
document.documentElement.setAttribute('data-theme', newTheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
darkModeQuery.addEventListener('change', handleThemeChange)
|
||||||
|
|
||||||
|
// Update time every minute
|
||||||
|
const timer = setInterval(() => setCurrentTime(new Date()), 60000)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
darkModeQuery.removeEventListener('change', handleThemeChange)
|
||||||
|
clearInterval(timer)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const toggleSidebar = () => setSidebarOpen(!sidebarOpen)
|
||||||
|
|
||||||
|
const formatTime = (date) => {
|
||||||
|
return date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' })
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
|
||||||
|
|
||||||
|
<main className="flex-1 overflow-y-auto p-4 md:p-6 no-scrollbar">
|
||||||
|
{/* Welcome Section */}
|
||||||
|
<div className="mb-4">
|
||||||
|
<h1 className="text-3xl font-bold">Dashboard</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Widgets Grid */}
|
||||||
|
<div className="mb-4">
|
||||||
|
<p className="text-2xl mb-3 font-bold">Overview</p>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-3 gap-4 ">
|
||||||
|
<Widget
|
||||||
|
title="Logged Hours Today"
|
||||||
|
value="6h 24m"
|
||||||
|
subtitle="Today's total time"
|
||||||
|
icon={<Clock size={24} />}
|
||||||
|
trend={12}
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
<Widget
|
||||||
|
title="Logged Hours This Week"
|
||||||
|
value="4h 48m"
|
||||||
|
subtitle="75% productivity"
|
||||||
|
icon={<TrendingUp size={24} />}
|
||||||
|
trend={8}
|
||||||
|
color="success"
|
||||||
|
/>
|
||||||
|
<Widget
|
||||||
|
title="Active Project"
|
||||||
|
value="VA Tracker Project"
|
||||||
|
subtitle="Until day ends"
|
||||||
|
icon={<ProjectorIcon size={24} />}
|
||||||
|
color="warning"
|
||||||
|
/>
|
||||||
|
{/* <Widget
|
||||||
|
title="Status"
|
||||||
|
value="Online"
|
||||||
|
subtitle={`Active since ${formatTime(new Date(currentTime.getTime() - 6.4 * 60 * 60 * 1000))}`}
|
||||||
|
icon={<Activity size={24} />}
|
||||||
|
color="success"
|
||||||
|
/> */}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-4">
|
||||||
|
<p className="text-2xl mb-3 font-bold flex justify-between">
|
||||||
|
<span>Projects</span>
|
||||||
|
<div>
|
||||||
|
<button className="ms-2 btn btn-sm btn-success btn-wide sm:w-auto rounded-full shadow-md hover:shadow-lg hover:scale-105 transition-all">
|
||||||
|
<Play className="w-5 h-5" />
|
||||||
|
Start Tracking
|
||||||
|
</button>
|
||||||
|
<button className="ms-2 btn btn-sm btn-error btn-wide sm:w-auto rounded-full shadow-md hover:shadow-lg hover:scale-105 transition-all">
|
||||||
|
<Square className="w-5 h-5" />
|
||||||
|
Stop Tracking
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
className="overflow-y-auto no-scrollbar overflow-x-auto rounded-box border border-base-content/10 bg-base-100 shadow-md"
|
||||||
|
style={{ minHeight: '100px', maxHeight: '250px' }}
|
||||||
|
>
|
||||||
|
<table className="table">
|
||||||
|
{/* Table Head */}
|
||||||
|
<thead className="sticky top-0 bg-base-200 z-10">
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>Project Name</th>
|
||||||
|
<th>Client Name</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
{/* Table Body */}
|
||||||
|
<tbody>
|
||||||
|
{projectsData.map((project) => (
|
||||||
|
<tr key={project.id}>
|
||||||
|
<th>{project.id}</th>
|
||||||
|
<td>{project.projectName}</td>
|
||||||
|
<td>{project.clientName}</td>
|
||||||
|
<td>
|
||||||
|
<div
|
||||||
|
className={`badge ${
|
||||||
|
project.status === 'Active'
|
||||||
|
? 'badge-success'
|
||||||
|
: project.status === 'Pending'
|
||||||
|
? 'badge-warning'
|
||||||
|
: project.status === 'Completed'
|
||||||
|
? 'badge-info'
|
||||||
|
: 'badge-neutral'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{project.status}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div
|
||||||
|
className={`badge ${
|
||||||
|
project.action === 'View Report'
|
||||||
|
? 'badge-info'
|
||||||
|
: project.action === 'Resume'
|
||||||
|
? 'badge-secondary'
|
||||||
|
: 'badge-primary'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{project.action}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-full">
|
||||||
|
<p className="text-xl font-bold mb-4">Tracking</p>
|
||||||
|
|
||||||
|
<div className="card bg-base-100 shadow-md border border-base-200 transition-all duration-300 w-full">
|
||||||
|
<div className="card-body">
|
||||||
|
<div className="flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||||
|
<div className="text-center sm:text-left">
|
||||||
|
<p className="text-5xl font-extrabold text-success tracking-tight">0h 15m</p>
|
||||||
|
<p className="text-sm text-base-content/70 mt-1">Current Session</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button className="btn btn-success btn-lg rounded-xl hover:scale-105 transition-transform duration-200 w-full sm:w-auto">
|
||||||
|
Start
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="divider my-4"></div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 text-sm">
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span className="text-base-content/70">Project:</span>
|
||||||
|
<span className="font-semibold text-base-content">Employee Tracker</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span className="text-base-content/70">Status:</span>
|
||||||
|
<span className="badge badge-success">Active</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Dashboard
|
||||||
|
|
@ -0,0 +1,150 @@
|
||||||
|
import React, { useState, useEffect, useRef } from 'react'
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
|
||||||
|
const Login = ({ setUser }) => {
|
||||||
|
const [formData, setFormData] = useState({ email: 'jai@gmail.com', password: '12345' })
|
||||||
|
const inputRef = useRef(null)
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
|
const handleChange = (e) => {
|
||||||
|
setFormData({ ...formData, [e.target.name]: e.target.value })
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
const { email, password } = formData
|
||||||
|
|
||||||
|
if (email === 'jai@gmail.com' && password === '12345') {
|
||||||
|
// alert('✅ Login successful!')
|
||||||
|
console.log('Login successful:', formData)
|
||||||
|
// Redirect or further logic here
|
||||||
|
localStorage.setItem("user", JSON.stringify(formData));
|
||||||
|
setUser(formData);
|
||||||
|
navigate('/')
|
||||||
|
} else {
|
||||||
|
alert('❌ Invalid email or password!')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ⌨️ Detect Enter key press
|
||||||
|
useEffect(() => {
|
||||||
|
const handleEnter = (e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
handleSubmit(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const inputElement = inputRef.current
|
||||||
|
inputElement?.addEventListener('keydown', handleEnter)
|
||||||
|
|
||||||
|
return () => inputElement?.removeEventListener('keydown', handleEnter)
|
||||||
|
}, [formData])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-primary/10 via-base-200 to-secondary/10 p-4">
|
||||||
|
{/* Login Card */}
|
||||||
|
<div className="card w-full max-w-md shadow-2xl bg-base-100 backdrop-blur-sm">
|
||||||
|
<div className="card-body">
|
||||||
|
{/* Avatar */}
|
||||||
|
<div className="flex justify-center items-center mb-4">
|
||||||
|
<div className="avatar placeholder">
|
||||||
|
<div className="bg-primary text-primary-content rounded-full w-16 h-16 flex items-center justify-center">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
className="w-8 h-8 stroke-current"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Title */}
|
||||||
|
<h2 className="text-center text-3xl font-bold mb-2">Welcome Back</h2>
|
||||||
|
<p className="text-center text-base-content/60 mb-6">Sign in to Employee Tracker</p>
|
||||||
|
|
||||||
|
{/* Form */}
|
||||||
|
<form
|
||||||
|
ref={inputRef}
|
||||||
|
className="flex flex-col items-center justify-center w-full"
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
>
|
||||||
|
{/* Email */}
|
||||||
|
<div className="form-control w-90 px-5">
|
||||||
|
<label className="label">
|
||||||
|
<span className="label-text font-medium">Email Address</span>
|
||||||
|
</label>
|
||||||
|
<label className="input input-bordered flex items-center gap-2">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
fill="currentColor"
|
||||||
|
className="w-4 h-4 opacity-70"
|
||||||
|
>
|
||||||
|
<path d="M2.5 3A1.5 1.5 0 0 0 1 4.5v.793c.026.009.051.02.076.032L7.674 8.51c.206.1.446.1.652 0l6.598-3.185A.755.755 0 0 1 15 5.293V4.5A1.5 1.5 0 0 0 13.5 3h-11Z" />
|
||||||
|
<path d="M15 6.954 8.978 9.86a2.25 2.25 0 0 1-1.956 0L1 6.954V11.5A1.5 1.5 0 0 0 2.5 13h11a1.5 1.5 0 0 0 1.5-1.5V6.954Z" />
|
||||||
|
</svg>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
name="email"
|
||||||
|
placeholder="you@example.com"
|
||||||
|
className="grow"
|
||||||
|
value={formData.email}
|
||||||
|
onChange={handleChange}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Password */}
|
||||||
|
<div className="form-control w-90 px-5 mt-4">
|
||||||
|
<label className="label">
|
||||||
|
<span className="label-text font-medium">Password</span>
|
||||||
|
</label>
|
||||||
|
<label className="input input-bordered flex items-center gap-2">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
fill="currentColor"
|
||||||
|
className="w-4 h-4 opacity-70"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M14 6a4 4 0 0 1-4.899 3.899l-1.955 1.955a.5.5 0 0 1-.353.146H5v1.5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1-.5-.5v-2.293a.5.5 0 0 1 .146-.353l3.955-3.955A4 4 0 1 1 14 6Zm-4-2a.75.75 0 0 0 0 1.5.5.5 0 0 1 .5.5.75.75 0 0 0 1.5 0 2 2 0 0 0-2-2Z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
name="password"
|
||||||
|
placeholder="Enter your password"
|
||||||
|
className="grow"
|
||||||
|
value={formData.password}
|
||||||
|
onChange={handleChange}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Submit Button */}
|
||||||
|
<div className="form-control w-full px-5 mt-6">
|
||||||
|
<button type="submit" className="btn btn-primary w-full">
|
||||||
|
Sign In
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Login
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
const Projects = () => {
|
||||||
|
return (
|
||||||
|
<div>Projects</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Projects
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
// src/routes/ProtectedRoute.jsx
|
||||||
|
import React from "react";
|
||||||
|
import { Navigate } from "react-router-dom";
|
||||||
|
|
||||||
|
const ProtectedRoute = ({ user, children }) => {
|
||||||
|
if (!user) {
|
||||||
|
return <Navigate to="/login" replace />;
|
||||||
|
}
|
||||||
|
return children;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProtectedRoute;
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
// src/routes/PublicRoute.jsx
|
||||||
|
import React from "react";
|
||||||
|
import { Navigate } from "react-router-dom";
|
||||||
|
|
||||||
|
const PublicRoute = ({ user, children }) => {
|
||||||
|
if (user) {
|
||||||
|
return <Navigate to="/dashboard" replace />;
|
||||||
|
}
|
||||||
|
return children;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PublicRoute;
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react(),tailwindcss()],
|
||||||
base: './',
|
base: './',
|
||||||
build: {
|
build: {
|
||||||
outDir: 'dist-react',
|
outDir: 'dist-react',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue