forget password

This commit is contained in:
JaiPandey425 2025-11-12 19:50:42 +05:30
parent b448e2a253
commit b01ad415f1
14 changed files with 494 additions and 306 deletions

View File

@ -82,7 +82,7 @@ ipcMain.handle("get-project-list", async () => {
// console.log(" API /list/project response:", res); // console.log(" API /list/project response:", res);
// ✅ Return only serializable data // Return only serializable data
return JSON.parse(JSON.stringify(res)); return JSON.parse(JSON.stringify(res));
} catch (error: any) { } catch (error: any) {
console.error(" get-project-list error:", error); console.error(" get-project-list error:", error);
@ -180,7 +180,7 @@ ipcMain.handle("get-project-logs", async (event, { projectID, fromDate, toDate }
let url = `/report/project/${projectID}`; let url = `/report/project/${projectID}`;
console.log("url" , url)
// 🧠 Append query params only if dates are provided // 🧠 Append query params only if dates are provided
if (fromDate && toDate) { if (fromDate && toDate) {
@ -191,6 +191,8 @@ ipcMain.handle("get-project-logs", async (event, { projectID, fromDate, toDate }
url += `?${params.toString()}`; url += `?${params.toString()}`;
} }
console.log("url" , url)
const response = await api.get(url); const response = await api.get(url);
console.log("✅ response-logs:", response); console.log("✅ response-logs:", response);
@ -201,3 +203,18 @@ ipcMain.handle("get-project-logs", async (event, { projectID, fromDate, toDate }
} }
}); });
ipcMain.handle("forgot-password", async (event, email: string) => {
try {
const response = await api.post("/forgot-password", { email });
console.log(response , "Line response")
if (response) {
return { success: true, message: response.data?.message };
}
} catch (error: any) {
console.error("Forgot password error:", error.response?.data || error.message);
return {
success: false,
message: error.response?.data?.message || "Something went wrong",
};
}
});

View File

@ -90,6 +90,8 @@ function handleCloseEvents(mainWindow: BrowserWindow) {
}); });
} }
export function getMainWindow() { export function getMainWindow() {
return mainWindow; return mainWindow;
} }

View File

@ -5,6 +5,7 @@ console.log("setting PreLoad")
contextBridge.exposeInMainWorld('electron', { contextBridge.exposeInMainWorld('electron', {
login: (credentials: { email: string, password: string }) => ipcRenderer.invoke('login-request', credentials), login: (credentials: { email: string, password: string }) => ipcRenderer.invoke('login-request', credentials),
logout: () => ipcRenderer.invoke('logout-request'), logout: () => ipcRenderer.invoke('logout-request'),
forgotPassword: (email: string) => ipcRenderer.invoke("forgot-password", email),
getToken: (TOKEN_KEY: "ACCESS_TOKEN" | "REFRESH_TOKEN") => ipcRenderer.invoke('get-token', TOKEN_KEY), getToken: (TOKEN_KEY: "ACCESS_TOKEN" | "REFRESH_TOKEN") => ipcRenderer.invoke('get-token', TOKEN_KEY),
uploadScreenshot: () => ipcRenderer.invoke('upload-screenshot'), uploadScreenshot: () => ipcRenderer.invoke('upload-screenshot'),
getActiveAppDetails: () => ipcRenderer.invoke('get-active-app'), getActiveAppDetails: () => ipcRenderer.invoke('get-active-app'),

View File

@ -1,46 +1,38 @@
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect } from "react";
import { BrowserRouter, Route, Routes, Navigate } from "react-router-dom"; import { BrowserRouter, Route, Routes, Navigate } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import { AppDispatch, RootState } from "./store";
import { loadUserFromStorage } from "./store/userSlice";
import Login from "./pages/Login"; import Login from "./pages/Login";
import Dashboard from "./pages/Dashboard"; import Dashboard from "./pages/Dashboard";
import Projects from "./pages/Projects/Projects"; import Projects from "./pages/Projects/Projects";
import DashboardLayout from "./layout/DashboardLayout"; import DashboardLayout from "./layout/DashboardLayout";
import ProtectedRoute from "./routes/ProtectedRoute" import ProtectedRoute from "./routes/ProtectedRoute";
import PublicRoute from "./routes/PublicRoute"; import PublicRoute from "./routes/PublicRoute";
import { ThemeToggle } from "./helper/ThemeToggle" import { ThemeToggle } from "./helper/ThemeToggle";
import LeaveAndAdjustments from "./pages/LeaveAndAdjustments/LeaveAndAdjustments"; import LeaveAndAdjustments from "./pages/LeaveAndAdjustments/LeaveAndAdjustments";
import Timesheets from "./pages/Timesheets/Timesheets"; import Timesheets from "./pages/Timesheets/Timesheets";
import Settings from "./pages/Settings/Settings"; import Settings from "./pages/Settings/Settings";
import HelpSupport from "./pages/HelpSupport/HelpSupport"; import HelpSupport from "./pages/HelpSupport/HelpSupport";
interface User {
email: string;
password: string;
}
const App: React.FC = () => { const App: React.FC = () => {
const theme = ThemeToggle(); const theme = ThemeToggle();
const [user, setUser] = useState<User | null>(null); const dispatch = useDispatch<AppDispatch>();
// ✅ Get user directly from Redux
const user = useSelector((state: RootState) => state.user.user);
// Apply theme
useEffect(() => { useEffect(() => {
document.documentElement.setAttribute("data-theme", theme); document.documentElement.setAttribute("data-theme", theme);
}, [theme]); }, [theme]);
// Load user from localStorage // ✅ Load user from localStorage into Redux when app starts
useEffect(() => { useEffect(() => {
const storedUser = localStorage.getItem("user"); dispatch(loadUserFromStorage());
if (storedUser) { }, [dispatch]);
try {
setUser(JSON.parse(storedUser));
} catch (err) {
console.error("Invalid user in localStorage", err);
}
}
}, []);
console.log("App.tsx → user:", user);
return ( return (
<BrowserRouter> <BrowserRouter>
@ -50,7 +42,7 @@ const App: React.FC = () => {
path="/login" path="/login"
element={ element={
<PublicRoute user={user}> <PublicRoute user={user}>
<Login setUser={setUser} /> <Login />
</PublicRoute> </PublicRoute>
} }
/> />
@ -60,7 +52,7 @@ const App: React.FC = () => {
path="/dashboard" path="/dashboard"
element={ element={
<ProtectedRoute user={user}> <ProtectedRoute user={user}>
<DashboardLayout user={user} setUser={setUser} /> <DashboardLayout />
</ProtectedRoute> </ProtectedRoute>
} }
> >

View File

@ -28,10 +28,17 @@ import { useActiveAppTracker } from "../hooks/useActiveAppTracker";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { RootState } from "../store"; import { RootState } from "../store";
import { setProjectsDataSlice, setProjectSummarySlice, setSelectedProjectSlice } from "../store/projectSlice"; import { setProjectsDataSlice, setProjectSummarySlice, setSelectedProjectSlice } from "../store/projectSlice";
import { clearUser } from "../store/userSlice";
// interface User {
// email: string;
// password: string;
// }
interface User { interface User {
id: string;
name: string;
email: string; email: string;
password: string;
} }
// Updated Project interface to match the JSON structure // Updated Project interface to match the JSON structure
@ -70,12 +77,7 @@ export interface TrackingState {
projectsData: Project[]; projectsData: Project[];
} }
interface DashboardLayoutProps { const DashboardLayout = () => {
user: User | null;
setUser: React.Dispatch<React.SetStateAction<User | null>>;
}
const DashboardLayout: React.FC<DashboardLayoutProps> = ({ setUser }) => {
const [isSidebarOpen, setSidebarOpen] = useState(true); const [isSidebarOpen, setSidebarOpen] = useState(true);
const [isMobile, setIsMobile] = useState(window.innerWidth < 768); const [isMobile, setIsMobile] = useState(window.innerWidth < 768);
const navigate = useNavigate(); const navigate = useNavigate();
@ -167,23 +169,28 @@ const DashboardLayout: React.FC<DashboardLayoutProps> = ({ setUser }) => {
setSelectedProject(firstProject); setSelectedProject(firstProject);
dispatch(setSelectedProjectSlice(firstProject)); dispatch(setSelectedProjectSlice(firstProject));
// Convert totalWorkMs to seconds for elapsedTime
const initialElapsedTime = Math.floor(firstProject.totalWorkMs / 1000);
// Initialize tracking map for that project // Initialize tracking map for that project
setProjectTrackingMap((prev) => { setProjectTrackingMap((prev) => {
const updated = new Map(prev); const updated = new Map(prev);
if (!updated.has(firstProject.projectId)) { if (!updated.has(firstProject.projectId)) {
updated.set(firstProject.projectId, { updated.set(firstProject.projectId, {
elapsedTime: 0, elapsedTime:initialElapsedTime,
startTime: null, startTime: null,
endTime: null, endTime: null,
isTracking: false, isTracking: false,
isPaused: false, isPaused: false,
pauseStartTime: null, pauseStartTime: null,
totalPausedTime: 0, totalPausedTime: 0,
accumulatedTime: 0, accumulatedTime: initialElapsedTime,
}); });
} }
return updated; return updated;
}); });
// Set the initial elapsed time in the component state
setElapsedTime(initialElapsedTime);
} }
} else { } else {
console.warn("⚠️ Unexpected response:", res); console.warn("⚠️ Unexpected response:", res);
@ -203,12 +210,14 @@ const DashboardLayout: React.FC<DashboardLayoutProps> = ({ setUser }) => {
const handleLogout = async () => { const handleLogout = async () => {
try { try {
const res = await window.electron.logout(); const res = await window.electron.logout();
localStorage.removeItem("user"); if (res) {
setUser(null); dispatch(clearUser()); // ✅ Removes from Redux + localStorage
navigate("/login"); navigate("/login");
}
} catch (error) { } catch (error) {
console.error("Logout error:", error); console.error("Logout error:", error);
} }
}; };
// Sync global states with selected project tracking data // Sync global states with selected project tracking data
@ -432,29 +441,53 @@ const DashboardLayout: React.FC<DashboardLayoutProps> = ({ setUser }) => {
// Handle project selection // Handle project selection
const handleProjectSelect = (projectId: string) => { const handleProjectSelect = (projectId: string) => {
if (isTracking) return; if (isTracking) return; // prevent switching while tracking
const project = projectsData.find((p) => p.projectId === projectId); const project = projectsData.find((p) => p.projectId === projectId);
if (project) { if (!project) return;
setSelectedProject(project);
const projectData = projectTrackingMap.get(projectId); setSelectedProject(project);
if (projectData) { dispatch(setSelectedProjectSlice(project));
setElapsedTime(projectData.elapsedTime);
setStartTime(projectData.startTime); const existingData = projectTrackingMap.get(projectId);
setEndTime(projectData.endTime);
setIsTracking(projectData.isTracking); setProjectTrackingMap((prev) => {
setIsPaused(projectData.isPaused); const updated = new Map(prev);
} else {
setElapsedTime(0); // If project is not yet tracked, initialize it with totalWorkMs
if (!existingData) {
const initialElapsedTime = Math.floor((project.totalWorkMs || 0) / 1000);
updated.set(projectId, {
elapsedTime: initialElapsedTime,
startTime: null,
endTime: null,
isTracking: false,
isPaused: false,
pauseStartTime: null,
totalPausedTime: 0,
accumulatedTime: initialElapsedTime,
});
// Update component-level state
setElapsedTime(initialElapsedTime);
setStartTime(null); setStartTime(null);
setEndTime(null); setEndTime(null);
setIsTracking(false); setIsTracking(false);
setIsPaused(false); setIsPaused(false);
} else {
// If already tracked, restore its state
setElapsedTime(existingData.elapsedTime);
setStartTime(existingData.startTime);
setEndTime(existingData.endTime);
setIsTracking(existingData.isTracking);
setIsPaused(existingData.isPaused);
} }
}
return updated;
});
}; };
const trackingState: TrackingState = { const trackingState: TrackingState = {
selectedProject, selectedProject,
projectTrackingMap, projectTrackingMap,
@ -557,11 +590,11 @@ const DashboardLayout: React.FC<DashboardLayoutProps> = ({ setUser }) => {
} }
{/* Layout Body */} {/* Layout Body */}
<div className={`flex ${!isTracking ? "max-h-screen" : " h-[calc(100vh-3rem)]"} bg-base-200 overflow-hidden`}> <div className={`flex ${!isTracking ? "max-h-screen min-h-screen" : " h-[calc(100vh-3rem)]"} bg-base-200 overflow-hidden`}>
{/* Sidebar */} {/* Sidebar */}
<aside className="w-20 bg-base-100 border-r border-base-300 flex-shrink-0 flex flex-col items-center py-4 space-y-4"> <aside className="w-20 bg-base-100 border-r border-base-300 flex-shrink-0 flex flex-col items-center py-4 space-y-4">
{menuItems.map((item) => ( {menuItems.map((item , index) => (
<div className="tooltip z-100 tooltip-right" data-tip={item.label}> <div className="tooltip z-100 tooltip-right" data-tip={item.label} key={index}>
<NavLink <NavLink
key={item.label} key={item.label}
to={item.path} to={item.path}

View File

@ -1,6 +1,7 @@
import React from "react"; import React from "react";
import { Clock, TrendingUp, Play, Square, ProjectorIcon, Pause } from "lucide-react"; import { Clock, TrendingUp, Play, Square, ProjectorIcon, Pause } from "lucide-react";
import { useOutletContext } from "react-router-dom"; import { useOutletContext } from "react-router-dom";
import NoData from "./NoData";
// Updated Project interface to match JSON structure // Updated Project interface to match JSON structure
interface Project { interface Project {
@ -95,18 +96,18 @@ const Dashboard: React.FC = () => {
: null; : null;
return ( return (
<main className="flex-1 overflow-y-auto no-scrollbar"> <main className="flex-1 overflow-hidden no-scrollbar max-h-screen">
{/* Header */} {/* Header */}
<div className="mb-2"> <div className="mb-2">
<h1 className="text-md font-bold">Dashboard</h1> <h1 className="text-md font-bold">Dashboard</h1>
<button {/* <button
onClick={async () => { onClick={async () => {
await window.electron.uploadScreenshot(); await window.electron.uploadScreenshot();
}} }}
className="btn btn-xs btn-outline mt-2" className="btn btn-xs btn-outline mt-2"
> >
Screenshot Test Screenshot Test
</button> </button> */}
</div> </div>
{/* Widgets */} {/* Widgets */}
@ -137,18 +138,25 @@ const Dashboard: React.FC = () => {
<span className="text-md font-bold">Tracking</span> <span className="text-md font-bold">Tracking</span>
</div> */} </div> */}
<div className="card bg-base-100 border border-base-200 shadow-sm"> <div className="card bg-base-100 border border-base-200 shadow-sm card-sm">
<div className="card-body"> <div className="card-body">
{selectedProject ? ( {selectedProject ? (
<> <>
<div className="flex flex-col sm:flex-row items-center justify-between gap-4"> <div className="flex flex-col sm:flex-row items-center justify-between gap-3">
<div className="text-center sm:text-left"> <div className="text-center sm:text-left">
<p className="text-lg font-extrabold text-success"> <p className="text-lg font-extrabold text-success">
{formatTime(selectedData?.elapsedTime || 0)} {formatTime(selectedData?.elapsedTime || 0)}
</p> </p>
<p className="text-xs text-base-content/70 mt-1"> {/* <p className="text-xs text-base-content/70 mt-1">
{selectedData?.isTracking ? "Current Session" : "Ready"} {selectedData?.isTracking ? "Current Session" : "Ready"}
</p> </p> */}
<div>
<span className="text-base-content/70">Project:</span>{" "}
<span className="font-semibold">
{selectedProject.projectName}
</span>
</div>
</div> </div>
{!selectedData?.isTracking ? ( {!selectedData?.isTracking ? (
@ -169,15 +177,9 @@ const Dashboard: React.FC = () => {
)} )}
</div> </div>
<div className="divider my-3" /> <div className="divider my-1" />
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3 text-sm"> {/* <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3 text-sm">
<div>
<span className="text-base-content/70">Project:</span>{" "}
<span className="font-semibold">
{selectedProject.projectName}
</span>
</div>
<div> <div>
<span className="text-base-content/70">Status:</span>{" "} <span className="text-base-content/70">Status:</span>{" "}
<span <span
@ -214,7 +216,7 @@ const Dashboard: React.FC = () => {
: "—"} : "—"}
</span> </span>
</div> </div>
</div> </div> */}
</> </>
) : ( ) : (
<p className="text-sm text-base-content/60 text-center"> <p className="text-sm text-base-content/60 text-center">
@ -226,12 +228,22 @@ const Dashboard: React.FC = () => {
</div> </div>
{/* Projects Table */} {/* Projects Table */}
<div> <div className=" border border-base-content/10 bg-base-100 shadow-md min-h-[58vh] flex flex-col">
<p className="text-md mb-2 font-bold">Projects</p> {/* 🔹 Sticky Header */}
<div className="relative rounded-box border border-base-content/10 bg-base-100 shadow-md min-h-[62vh] max-h-[62vh]"> <div className=" sticky top-0 z-20 bg-base-100 border-b border-base-content/10 p-3 flex justify-between items-center">
<p className="text-md font-bold">Projects</p>
</div>
{/* 🔹 Table or Empty State */}
{projectsData.length > 0 ? ( {projectsData.length > 0 ? (
<div className="overflow-y-auto h-full"> <div
<table className="table table-zebra w-full text-sm"> className="overflow-y-auto"
style={{
maxHeight: "50vh", // scroll only after this height
minHeight: "auto", // auto adjusts if few rows
}}
>
<table className="table table-zebra w-full text-[12px]">
<thead className="sticky top-0 bg-base-200 z-10"> <thead className="sticky top-0 bg-base-200 z-10">
<tr> <tr>
<th>#</th> <th>#</th>
@ -242,14 +254,17 @@ const Dashboard: React.FC = () => {
</thead> </thead>
<tbody> <tbody>
{projectsData.map((project, index) => { {projectsData.map((project, index) => {
const isCurrentlySelected = selectedProject?.projectId === project.projectId; const isCurrentlySelected =
selectedProject?.projectId === project.projectId;
const isDisabled = isTracking && !isCurrentlySelected; const isDisabled = isTracking && !isCurrentlySelected;
return ( return (
<tr key={project.projectId}> <tr key={project.projectId}>
<td className="text-xs opacity-70">{index + 1}</td> <td className="text-xs opacity-70">{index + 1}</td>
<td className="font-medium">{project.projectName}</td> <td className="font-medium">{project.projectName}</td>
<td className="font-mono text-xs">{project.totalWorkTime}</td> <td className="font-mono text-xs">
{project.totalWorkTime || "0h 0m 0s"}
</td>
<td> <td>
<button <button
className={`btn btn-xs ${isCurrentlySelected ? "btn-success" : "btn-primary" className={`btn btn-xs ${isCurrentlySelected ? "btn-success" : "btn-primary"
@ -267,13 +282,13 @@ const Dashboard: React.FC = () => {
</table> </table>
</div> </div>
) : ( ) : (
<div className="flex items-center justify-center h-full text-sm text-base-content/60"> <div className="flex items-center justify-center flex-1 text-sm text-base-content/60">
<p className="mt-10 font-bold text-lg">No projects found.</p> <NoData/>
</div> </div>
)} )}
</div> </div>
</div>
</main> </main>
); );
}; };

View File

@ -249,7 +249,7 @@ const LeaveAndAdjustments: React.FC = () => {
</div> </div>
{/* Tab Content */} {/* Tab Content */}
<div className="px-8 py-4 overflow-y-auto no-scrollbar max-h-[80vh]"> <div className="px-8 py-4 overflow-y-auto no-scrollbar max-h-[81vh]">
{/* PTO Tab */} {/* PTO Tab */}
{activeTab === 'create_and_adjust_timesheet' && ( {activeTab === 'create_and_adjust_timesheet' && (
<div className="animate-fade-in"> <div className="animate-fade-in">

View File

@ -1,44 +1,72 @@
import React, { useState, useEffect, useRef, FormEvent, ChangeEvent } from 'react' import React, { useState, useEffect, useRef, FormEvent, ChangeEvent } from 'react'
import { useDispatch } from 'react-redux';
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { setUserSlice } from '../store/userSlice';
interface User { const Login = () => {
email: string
password: string
}
interface LoginProps {
setUser: (user: User) => void
}
const Login: React.FC<LoginProps> = ({ setUser }) => {
const [formData, setFormData] = useState<User>({ email: '', password: '' })
const inputRef = useRef<HTMLFormElement | null>(null) const inputRef = useRef<HTMLFormElement | null>(null)
const navigate = useNavigate() const navigate = useNavigate()
const dispatch = useDispatch();
const [errorMessage, setErrorMessage] = useState("")
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [isForgotMode, setIsForgotMode] = useState(false);
const [loading, setLoading] = useState(false);
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
setFormData({ ...formData, [e.target.name]: e.target.value })
}
const handleSubmit = async (e: FormEvent<HTMLFormElement> | KeyboardEvent) => { const handleLogin = async (e: FormEvent<HTMLFormElement> | KeyboardEvent) => {
e.preventDefault() e.preventDefault()
const { email, password } = formData
if (email === "" || password === "") return if (email === "" || password === "") return
setLoading(true);
try {
const response = await window.electron.login({ email, password }); const response = await window.electron.login({ email, password });
console.log(response) console.log(response)
if (response.success) { if (response.success) {
localStorage.setItem("user", JSON.stringify(formData)); // setUser(response.user);
setUser(response.user); dispatch(setUserSlice({
id: response.user.id,
name: response.user.name,
email: response.user.email,
}));
navigate("/") navigate("/")
} else if (response.success == false) {
console.log(response.error.message)
setErrorMessage(response.error.message)
} }
} catch (error) {
console.log(error)
} finally {
setLoading(false);
} }
}
const handleForgotPassword = async (e: FormEvent<HTMLFormElement> | KeyboardEvent) => {
e.preventDefault();
setLoading(true);
try {
const res = await window.electron.forgotPassword(email);
console.log("Forgot password response:", res);
alert("Password reset link sent to your email");
setIsForgotMode(false);
} catch (error) {
console.error("Forgot password error:", error);
} finally {
setLoading(false);
}
};
// Detect Enter key press // Detect Enter key press
useEffect(() => { useEffect(() => {
const handleEnter = (e: KeyboardEvent) => { const handleEnter = (e: KeyboardEvent) => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
handleSubmit(e) if (!isForgotMode) {
handleLogin(e)
} else {
handleForgotPassword(e)
}
} }
} }
@ -46,9 +74,10 @@ const Login: React.FC<LoginProps> = ({ setUser }) => {
inputElement?.addEventListener('keydown', handleEnter) inputElement?.addEventListener('keydown', handleEnter)
return () => inputElement?.removeEventListener('keydown', handleEnter) return () => inputElement?.removeEventListener('keydown', handleEnter)
}, [formData]) }, [email, password])
return ( 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"> <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">
<div className="card w-full max-w-md shadow-2xl bg-base-100 backdrop-blur-sm"> <div className="card w-full max-w-md shadow-2xl bg-base-100 backdrop-blur-sm">
<div className="card-body"> <div className="card-body">
@ -77,7 +106,7 @@ const Login: React.FC<LoginProps> = ({ setUser }) => {
<form <form
ref={inputRef} ref={inputRef}
className="flex flex-col items-center justify-center w-full" className="flex flex-col items-center justify-center w-full"
onSubmit={handleSubmit} onSubmit={isForgotMode ? handleForgotPassword : handleLogin}
> >
<div className="form-control w-90 px-5"> <div className="form-control w-90 px-5">
<label className="label"> <label className="label">
@ -97,14 +126,14 @@ const Login: React.FC<LoginProps> = ({ setUser }) => {
name="email" name="email"
placeholder="you@example.com" placeholder="you@example.com"
className="grow" className="grow"
value={formData.email} value={email}
onChange={handleChange} onChange={(e) => setEmail(e.target.value)}
required required
/> />
</label> </label>
</div> </div>
<div className="form-control w-90 px-5 mt-4"> {!isForgotMode && <div className="form-control w-90 px-5 mt-3">
<label className="label"> <label className="label">
<span className="label-text font-medium">Password</span> <span className="label-text font-medium">Password</span>
</label> </label>
@ -125,22 +154,51 @@ const Login: React.FC<LoginProps> = ({ setUser }) => {
name="password" name="password"
placeholder="Enter your password" placeholder="Enter your password"
className="grow" className="grow"
value={formData.password} value={password}
onChange={handleChange} onChange={(e) => setPassword(e.target.value)}
required required
/> />
</label> </label>
</div> </div>}
<div className="form-control w-full px-5 mt-6"> <div className="form-control w-90 px-5 mt-4">
<button type="submit" className="btn btn-primary w-full"> <button
Sign In type="submit"
disabled={loading}
className="btn btn-primary w-full"
>
{loading
? "Please wait..."
: isForgotMode
? "Send Reset Link"
: "Login"}
</button> </button>
</div> </div>
<div className="text-center mt-4">
{isForgotMode ? (
<button
type="button"
onClick={() => setIsForgotMode(false)}
className="link link-primary text-sm"
>
Back to Login
</button>
) : (
<button
type="button"
onClick={() => setIsForgotMode(true)}
className="link link-primary text-sm"
>
Forgot Password?
</button>
)}
</div>
{errorMessage && <span className='text-red-500 font-semibold mt-2'>{errorMessage}...!</span>}
</form> </form>
</div> </div>
</div> </div>
</div> </div>
</>
) )
} }

65
src/ui/pages/NoData.tsx Normal file
View File

@ -0,0 +1,65 @@
import React from "react";
const NoData = ({ message = "No data available", subText = "Please add new items or check back later." }) => {
return (
<div className="flex flex-col items-center justify-center min-h-[67vh] p-6 text-center">
{/* SVG Illustration */}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 600 360"
className="w-64 h-40 mb-6 text-primary"
>
<rect
x="38"
y="56"
width="524"
height="208"
rx="14"
fill="currentColor"
opacity="0.05"
/>
<g transform="translate(125, 68)">
<rect
x="0"
y="24"
width="298"
height="150"
rx="8"
fill="white"
stroke="currentColor"
strokeWidth="2"
opacity="0.15"
/>
<g transform="translate(18, 43)" fill="currentColor" opacity="0.1">
<rect x="0" y="0" width="160" height="12" rx="6" />
<rect x="0" y="24" width="110" height="12" rx="6" />
<rect x="0" y="48" width="140" height="12" rx="6" />
</g>
<g
transform="translate(160, 72) scale(1.05)"
fill="none"
stroke="currentColor"
strokeWidth="10"
strokeLinecap="round"
strokeLinejoin="round"
className="animate-pulse"
>
<circle cx="24" cy="24" r="18" fill="none" />
<path d="M39 39 L58 58" />
</g>
</g>
</svg>
{/* Text section */}
<div className="card bg-base-100 w-full max-w-md">
<div className="card-body items-center">
<h2 className="card-title text-lg font-semibold text-base-content">
{message}
</h2>
</div>
</div>
</div>
);
};
export default NoData;

View File

@ -1,36 +1,29 @@
import React from "react"; import React, { useEffect, useState } from "react";
import { User, Mail, Lock, Edit2, Shield, CalendarDays } from "lucide-react"; import { User, Mail } from "lucide-react";
import { useSelector } from "react-redux";
import { RootState } from "../../store";
interface UserProfile { interface User {
id: string;
name: string; name: string;
email: string; email: string;
role: string;
employeeId: string;
joinDate: string;
department: string;
profileImage?: string;
} }
const userData: UserProfile = {
name: "Jai Pandey",
email: "jai.pandey@vatechcorp.com",
role: "Frontend Developer",
employeeId: "EMP-1023",
joinDate: "2022-07-15",
department: "Engineering",
profileImage: "https://ui-avatars.com/api/?name=Jai+Pandey&background=random",
};
const Settings: React.FC = () => { const Settings: React.FC = () => {
const user = useSelector((state: RootState) => state.user.user);
console.log(user , "Line 16")
return ( return (
<div className="flex flex-col gap-2 max-h-screen"> <div className="flex flex-col gap-2 ">
{/* Header */} {/* Header */}
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h2 className="text-md font-bold flex items-center gap-2"> <h2 className="text-md font-bold flex items-center gap-2">
Profile Settings Profile Settings
</h2> </h2>
<button className="btn btn-sm btn-primary gap-1"> <button className="btn btn-sm btn-outline btn-primary">
<Edit2 size={16} /> Edit Profile Change Password
</button> </button>
</div> </div>
@ -43,8 +36,7 @@ const Settings: React.FC = () => {
<User size={60} /> <User size={60} />
</div> </div>
</div> </div>
<h3 className="mt-3 text-lg font-semibold">{userData.name}</h3> <h3 className="mt-3 text-lg font-semibold">{user?.name}</h3>
<p className="text-sm text-base-content/70">{userData.role}</p>
</div> </div>
<div className="divider md:divider-horizontal" /> <div className="divider md:divider-horizontal" />
@ -55,51 +47,8 @@ const Settings: React.FC = () => {
<p className="text-xs text-base-content/70 flex items-center gap-2"> <p className="text-xs text-base-content/70 flex items-center gap-2">
<Mail size={14} /> Email <Mail size={14} /> Email
</p> </p>
<p className="text-sm font-medium">{userData.email}</p> <p className="text-sm font-medium">{user?.email}</p>
</div> </div>
<div className="p-2 rounded-md bg-base-200/50">
<p className="text-xs text-base-content/70 flex items-center gap-2">
<Shield size={14} /> Role
</p>
<p className="text-sm font-medium">{userData.role}</p>
</div>
<div className="p-2 rounded-md bg-base-200/50">
<p className="text-xs text-base-content/70 flex items-center gap-2">
<Lock size={14} /> Employee ID
</p>
<p className="text-sm font-medium">{userData.employeeId}</p>
</div>
<div className="p-2 rounded-md bg-base-200/50">
<p className="text-xs text-base-content/70 flex items-center gap-2">
<CalendarDays size={14} /> Joining Date
</p>
<p className="text-sm font-medium">{userData.joinDate}</p>
</div>
<div className="p-2 rounded-md bg-base-200/50 sm:col-span-2">
<p className="text-xs text-base-content/70 flex items-center gap-2">
<User size={14} /> Department
</p>
<p className="text-sm font-medium">{userData.department}</p>
</div>
</div>
</div>
{/* Account Settings */}
<div className="card bg-base-100 border border-base-300 shadow-sm p-4">
<h3 className="text-md font-semibold mb-3">Account Security</h3>
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between border-t border-base-300 pt-3">
<p className="text-sm flex items-center gap-2">
<Lock size={16} /> Password last changed on{" "}
<span className="font-medium text-base-content">Aug 12, 2024</span>
</p>
<button className="btn btn-sm btn-outline btn-primary">
Change Password
</button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -72,7 +72,7 @@ const ProjectLogsModal: React.FC<Props> = ({ isOpen, onClose, projectName, logs,
{tab.title} <br/> {tab.title} <br/>
{tab.appName} {tab.appName}
</td> </td>
<td><span className="text-green-500">{new Date(tab.from).toLocaleTimeString()}</span> <span className="text-red-600">{new Date(tab.to).toLocaleTimeString()}</span></td> <td><span className="text-green-500">{new Date(tab.from).toLocaleTimeString()}</span> <br/><span className="text-red-600">{new Date(tab.to).toLocaleTimeString()}</span></td>
<td>{tab.durationFormatted}</td> <td>{tab.durationFormatted}</td>
</tr> </tr>
))} ))}

View File

@ -2,11 +2,13 @@
import { configureStore } from "@reduxjs/toolkit"; import { configureStore } from "@reduxjs/toolkit";
import windowReducer from "./windowSlice"; import windowReducer from "./windowSlice";
import projectsReducer from "./projectSlice"; import projectsReducer from "./projectSlice";
import userReducer from "./userSlice";
export const store = configureStore({ export const store = configureStore({
reducer: { reducer: {
window: windowReducer, window: windowReducer,
projects: projectsReducer, projects: projectsReducer,
user: userReducer,
}, },
}); });

View File

@ -0,0 +1,50 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
// Define and export the User type so other files can use it
export interface User {
id: string;
name: string;
email: string;
}
// Define the state shape
interface UserState {
user: User | null;
}
const initialState: UserState = {
user: null,
};
// Create the slice
const userSlice = createSlice({
name: "user",
initialState,
reducers: {
setUserSlice: (state, action: PayloadAction<User>) => {
state.user = action.payload;
// Save to localStorage
localStorage.setItem("user", JSON.stringify(action.payload));
},
clearUser: (state) => {
state.user = null;
localStorage.removeItem("user");
},
loadUserFromStorage: (state) => {
const storedUser = localStorage.getItem("user");
if (storedUser) {
try {
state.user = JSON.parse(storedUser) as User;
} catch (error) {
console.error("Invalid user in localStorage:", error);
localStorage.removeItem("user");
}
}
},
},
});
// Export actions and reducer
export const { setUserSlice, clearUser, loadUserFromStorage } = userSlice.actions;
export default userSlice.reducer;

6
types.d.ts vendored
View File

@ -67,6 +67,10 @@ interface Window {
electron: { electron: {
login: (credentials: { email: string; password: string }) => Promise<any>; login: (credentials: { email: string; password: string }) => Promise<any>;
logout: () => Promise<any>; logout: () => Promise<any>;
forgotPassword: (email: string) => Promise<{
success: boolean;
message: string;
}>;
getToken: (tokenKey: "ACCESS_TOKEN" | "REFRESH_TOKEN") => Promise<string | null>; getToken: (tokenKey: "ACCESS_TOKEN" | "REFRESH_TOKEN") => Promise<string | null>;
uploadScreenshot: () => {}; uploadScreenshot: () => {};
getActiveAppDetails: () => {}; getActiveAppDetails: () => {};
@ -94,7 +98,7 @@ interface Window {
// getProjectLogs: (projectId: string) => Promise<{ success: boolean; logs: ProjectLogResponse[] }>; // getProjectLogs: (projectId: string) => Promise<{ success: boolean; logs: ProjectLogResponse[] }>;
getProjectLogs: ( getProjectLogs: (
projectID: string, projectId: string,
fromDate?: string, fromDate?: string,
toDate?: string toDate?: string
) => Promise<{ success: boolean; logs: ProjectLogResponse[] }>; ) => Promise<{ success: boolean; logs: ProjectLogResponse[] }>;