forget password
This commit is contained in:
parent
b448e2a253
commit
b01ad415f1
|
|
@ -82,7 +82,7 @@ ipcMain.handle("get-project-list", async () => {
|
|||
|
||||
// console.log(" API /list/project response:", res);
|
||||
|
||||
// ✅ Return only serializable data
|
||||
// Return only serializable data
|
||||
return JSON.parse(JSON.stringify(res));
|
||||
} catch (error: any) {
|
||||
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}`;
|
||||
|
||||
console.log("url" , url)
|
||||
|
||||
|
||||
// 🧠 Append query params only if dates are provided
|
||||
if (fromDate && toDate) {
|
||||
|
|
@ -191,6 +191,8 @@ ipcMain.handle("get-project-logs", async (event, { projectID, fromDate, toDate }
|
|||
url += `?${params.toString()}`;
|
||||
}
|
||||
|
||||
console.log("url" , url)
|
||||
|
||||
const response = await api.get(url);
|
||||
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",
|
||||
};
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -90,6 +90,8 @@ function handleCloseEvents(mainWindow: BrowserWindow) {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function getMainWindow() {
|
||||
return mainWindow;
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ console.log("setting PreLoad")
|
|||
contextBridge.exposeInMainWorld('electron', {
|
||||
login: (credentials: { email: string, password: string }) => ipcRenderer.invoke('login-request', credentials),
|
||||
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),
|
||||
uploadScreenshot: () => ipcRenderer.invoke('upload-screenshot'),
|
||||
getActiveAppDetails: () => ipcRenderer.invoke('get-active-app'),
|
||||
|
|
|
|||
|
|
@ -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 { useDispatch, useSelector } from "react-redux";
|
||||
import { AppDispatch, RootState } from "./store";
|
||||
import { loadUserFromStorage } from "./store/userSlice";
|
||||
|
||||
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 ProtectedRoute from "./routes/ProtectedRoute";
|
||||
import PublicRoute from "./routes/PublicRoute";
|
||||
import { ThemeToggle } from "./helper/ThemeToggle"
|
||||
import { ThemeToggle } from "./helper/ThemeToggle";
|
||||
import LeaveAndAdjustments from "./pages/LeaveAndAdjustments/LeaveAndAdjustments";
|
||||
import Timesheets from "./pages/Timesheets/Timesheets";
|
||||
import Settings from "./pages/Settings/Settings";
|
||||
import HelpSupport from "./pages/HelpSupport/HelpSupport";
|
||||
|
||||
interface User {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
|
||||
const App: React.FC = () => {
|
||||
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(() => {
|
||||
document.documentElement.setAttribute("data-theme", theme);
|
||||
}, [theme]);
|
||||
|
||||
// Load user from localStorage
|
||||
// ✅ Load user from localStorage into Redux when app starts
|
||||
useEffect(() => {
|
||||
const storedUser = localStorage.getItem("user");
|
||||
if (storedUser) {
|
||||
try {
|
||||
setUser(JSON.parse(storedUser));
|
||||
} catch (err) {
|
||||
console.error("Invalid user in localStorage", err);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
dispatch(loadUserFromStorage());
|
||||
}, [dispatch]);
|
||||
|
||||
console.log("App.tsx → user:", user);
|
||||
|
||||
return (
|
||||
<BrowserRouter>
|
||||
|
|
@ -50,7 +42,7 @@ const App: React.FC = () => {
|
|||
path="/login"
|
||||
element={
|
||||
<PublicRoute user={user}>
|
||||
<Login setUser={setUser} />
|
||||
<Login />
|
||||
</PublicRoute>
|
||||
}
|
||||
/>
|
||||
|
|
@ -60,7 +52,7 @@ const App: React.FC = () => {
|
|||
path="/dashboard"
|
||||
element={
|
||||
<ProtectedRoute user={user}>
|
||||
<DashboardLayout user={user} setUser={setUser} />
|
||||
<DashboardLayout />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -28,10 +28,17 @@ import { useActiveAppTracker } from "../hooks/useActiveAppTracker";
|
|||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { RootState } from "../store";
|
||||
import { setProjectsDataSlice, setProjectSummarySlice, setSelectedProjectSlice } from "../store/projectSlice";
|
||||
import { clearUser } from "../store/userSlice";
|
||||
|
||||
// interface User {
|
||||
// email: string;
|
||||
// password: string;
|
||||
// }
|
||||
|
||||
interface User {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
// Updated Project interface to match the JSON structure
|
||||
|
|
@ -70,12 +77,7 @@ export interface TrackingState {
|
|||
projectsData: Project[];
|
||||
}
|
||||
|
||||
interface DashboardLayoutProps {
|
||||
user: User | null;
|
||||
setUser: React.Dispatch<React.SetStateAction<User | null>>;
|
||||
}
|
||||
|
||||
const DashboardLayout: React.FC<DashboardLayoutProps> = ({ setUser }) => {
|
||||
const DashboardLayout = () => {
|
||||
const [isSidebarOpen, setSidebarOpen] = useState(true);
|
||||
const [isMobile, setIsMobile] = useState(window.innerWidth < 768);
|
||||
const navigate = useNavigate();
|
||||
|
|
@ -167,23 +169,28 @@ const DashboardLayout: React.FC<DashboardLayoutProps> = ({ setUser }) => {
|
|||
setSelectedProject(firstProject);
|
||||
dispatch(setSelectedProjectSlice(firstProject));
|
||||
|
||||
// Convert totalWorkMs to seconds for elapsedTime
|
||||
const initialElapsedTime = Math.floor(firstProject.totalWorkMs / 1000);
|
||||
|
||||
// Initialize tracking map for that project
|
||||
setProjectTrackingMap((prev) => {
|
||||
const updated = new Map(prev);
|
||||
if (!updated.has(firstProject.projectId)) {
|
||||
updated.set(firstProject.projectId, {
|
||||
elapsedTime: 0,
|
||||
elapsedTime:initialElapsedTime,
|
||||
startTime: null,
|
||||
endTime: null,
|
||||
isTracking: false,
|
||||
isPaused: false,
|
||||
pauseStartTime: null,
|
||||
totalPausedTime: 0,
|
||||
accumulatedTime: 0,
|
||||
accumulatedTime: initialElapsedTime,
|
||||
});
|
||||
}
|
||||
return updated;
|
||||
});
|
||||
// Set the initial elapsed time in the component state
|
||||
setElapsedTime(initialElapsedTime);
|
||||
}
|
||||
} else {
|
||||
console.warn("⚠️ Unexpected response:", res);
|
||||
|
|
@ -203,12 +210,14 @@ const DashboardLayout: React.FC<DashboardLayoutProps> = ({ setUser }) => {
|
|||
const handleLogout = async () => {
|
||||
try {
|
||||
const res = await window.electron.logout();
|
||||
localStorage.removeItem("user");
|
||||
setUser(null);
|
||||
if (res) {
|
||||
dispatch(clearUser()); // ✅ Removes from Redux + localStorage
|
||||
navigate("/login");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Logout error:", error);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Sync global states with selected project tracking data
|
||||
|
|
@ -432,29 +441,53 @@ const DashboardLayout: React.FC<DashboardLayoutProps> = ({ setUser }) => {
|
|||
|
||||
// Handle project selection
|
||||
const handleProjectSelect = (projectId: string) => {
|
||||
if (isTracking) return;
|
||||
if (isTracking) return; // prevent switching while tracking
|
||||
|
||||
const project = projectsData.find((p) => p.projectId === projectId);
|
||||
if (project) {
|
||||
setSelectedProject(project);
|
||||
if (!project) return;
|
||||
|
||||
const projectData = projectTrackingMap.get(projectId);
|
||||
if (projectData) {
|
||||
setElapsedTime(projectData.elapsedTime);
|
||||
setStartTime(projectData.startTime);
|
||||
setEndTime(projectData.endTime);
|
||||
setIsTracking(projectData.isTracking);
|
||||
setIsPaused(projectData.isPaused);
|
||||
} else {
|
||||
setElapsedTime(0);
|
||||
setSelectedProject(project);
|
||||
dispatch(setSelectedProjectSlice(project));
|
||||
|
||||
const existingData = projectTrackingMap.get(projectId);
|
||||
|
||||
setProjectTrackingMap((prev) => {
|
||||
const updated = new Map(prev);
|
||||
|
||||
// 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);
|
||||
setEndTime(null);
|
||||
setIsTracking(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 = {
|
||||
selectedProject,
|
||||
projectTrackingMap,
|
||||
|
|
@ -557,11 +590,11 @@ const DashboardLayout: React.FC<DashboardLayoutProps> = ({ setUser }) => {
|
|||
}
|
||||
|
||||
{/* 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 */}
|
||||
<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) => (
|
||||
<div className="tooltip z-100 tooltip-right" data-tip={item.label}>
|
||||
{menuItems.map((item , index) => (
|
||||
<div className="tooltip z-100 tooltip-right" data-tip={item.label} key={index}>
|
||||
<NavLink
|
||||
key={item.label}
|
||||
to={item.path}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React from "react";
|
||||
import { Clock, TrendingUp, Play, Square, ProjectorIcon, Pause } from "lucide-react";
|
||||
import { useOutletContext } from "react-router-dom";
|
||||
import NoData from "./NoData";
|
||||
|
||||
// Updated Project interface to match JSON structure
|
||||
interface Project {
|
||||
|
|
@ -95,18 +96,18 @@ const Dashboard: React.FC = () => {
|
|||
: null;
|
||||
|
||||
return (
|
||||
<main className="flex-1 overflow-y-auto no-scrollbar">
|
||||
<main className="flex-1 overflow-hidden no-scrollbar max-h-screen">
|
||||
{/* Header */}
|
||||
<div className="mb-2">
|
||||
<h1 className="text-md font-bold">Dashboard</h1>
|
||||
<button
|
||||
{/* <button
|
||||
onClick={async () => {
|
||||
await window.electron.uploadScreenshot();
|
||||
}}
|
||||
className="btn btn-xs btn-outline mt-2"
|
||||
>
|
||||
Screenshot Test
|
||||
</button>
|
||||
</button> */}
|
||||
</div>
|
||||
|
||||
{/* Widgets */}
|
||||
|
|
@ -137,18 +138,25 @@ const Dashboard: React.FC = () => {
|
|||
<span className="text-md font-bold">Tracking</span>
|
||||
</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">
|
||||
{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">
|
||||
<p className="text-lg font-extrabold text-success">
|
||||
{formatTime(selectedData?.elapsedTime || 0)}
|
||||
</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"}
|
||||
</p>
|
||||
</p> */}
|
||||
|
||||
<div>
|
||||
<span className="text-base-content/70">Project:</span>{" "}
|
||||
<span className="font-semibold">
|
||||
{selectedProject.projectName}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!selectedData?.isTracking ? (
|
||||
|
|
@ -169,15 +177,9 @@ const Dashboard: React.FC = () => {
|
|||
)}
|
||||
</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>
|
||||
<span className="text-base-content/70">Project:</span>{" "}
|
||||
<span className="font-semibold">
|
||||
{selectedProject.projectName}
|
||||
</span>
|
||||
</div>
|
||||
{/* <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">Status:</span>{" "}
|
||||
<span
|
||||
|
|
@ -214,7 +216,7 @@ const Dashboard: React.FC = () => {
|
|||
: "—"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
</>
|
||||
) : (
|
||||
<p className="text-sm text-base-content/60 text-center">
|
||||
|
|
@ -226,12 +228,22 @@ const Dashboard: React.FC = () => {
|
|||
</div>
|
||||
|
||||
{/* Projects Table */}
|
||||
<div>
|
||||
<p className="text-md mb-2 font-bold">Projects</p>
|
||||
<div className="relative rounded-box border border-base-content/10 bg-base-100 shadow-md min-h-[62vh] max-h-[62vh]">
|
||||
<div className=" border border-base-content/10 bg-base-100 shadow-md min-h-[58vh] flex flex-col">
|
||||
{/* 🔹 Sticky Header */}
|
||||
<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 ? (
|
||||
<div className="overflow-y-auto h-full">
|
||||
<table className="table table-zebra w-full text-sm">
|
||||
<div
|
||||
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">
|
||||
<tr>
|
||||
<th>#</th>
|
||||
|
|
@ -242,14 +254,17 @@ const Dashboard: React.FC = () => {
|
|||
</thead>
|
||||
<tbody>
|
||||
{projectsData.map((project, index) => {
|
||||
const isCurrentlySelected = selectedProject?.projectId === project.projectId;
|
||||
const isCurrentlySelected =
|
||||
selectedProject?.projectId === project.projectId;
|
||||
const isDisabled = isTracking && !isCurrentlySelected;
|
||||
|
||||
return (
|
||||
<tr key={project.projectId}>
|
||||
<td className="text-xs opacity-70">{index + 1}</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>
|
||||
<button
|
||||
className={`btn btn-xs ${isCurrentlySelected ? "btn-success" : "btn-primary"
|
||||
|
|
@ -267,13 +282,13 @@ const Dashboard: React.FC = () => {
|
|||
</table>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-full text-sm text-base-content/60">
|
||||
<p className="mt-10 font-bold text-lg">No projects found.</p>
|
||||
<div className="flex items-center justify-center flex-1 text-sm text-base-content/60">
|
||||
<NoData/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -249,7 +249,7 @@ const LeaveAndAdjustments: React.FC = () => {
|
|||
</div>
|
||||
|
||||
{/* 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 */}
|
||||
{activeTab === 'create_and_adjust_timesheet' && (
|
||||
<div className="animate-fade-in">
|
||||
|
|
|
|||
|
|
@ -1,44 +1,72 @@
|
|||
import React, { useState, useEffect, useRef, FormEvent, ChangeEvent } from 'react'
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { setUserSlice } from '../store/userSlice';
|
||||
|
||||
interface User {
|
||||
email: string
|
||||
password: string
|
||||
}
|
||||
|
||||
interface LoginProps {
|
||||
setUser: (user: User) => void
|
||||
}
|
||||
|
||||
const Login: React.FC<LoginProps> = ({ setUser }) => {
|
||||
const [formData, setFormData] = useState<User>({ email: '', password: '' })
|
||||
const Login = () => {
|
||||
const inputRef = useRef<HTMLFormElement | null>(null)
|
||||
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()
|
||||
|
||||
const { email, password } = formData
|
||||
if (email === "" || password === "") return
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await window.electron.login({ email, password });
|
||||
console.log(response)
|
||||
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("/")
|
||||
} 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
|
||||
useEffect(() => {
|
||||
const handleEnter = (e: KeyboardEvent) => {
|
||||
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)
|
||||
|
||||
return () => inputElement?.removeEventListener('keydown', handleEnter)
|
||||
}, [formData])
|
||||
}, [email, password])
|
||||
|
||||
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="card w-full max-w-md shadow-2xl bg-base-100 backdrop-blur-sm">
|
||||
<div className="card-body">
|
||||
|
|
@ -77,7 +106,7 @@ const Login: React.FC<LoginProps> = ({ setUser }) => {
|
|||
<form
|
||||
ref={inputRef}
|
||||
className="flex flex-col items-center justify-center w-full"
|
||||
onSubmit={handleSubmit}
|
||||
onSubmit={isForgotMode ? handleForgotPassword : handleLogin}
|
||||
>
|
||||
<div className="form-control w-90 px-5">
|
||||
<label className="label">
|
||||
|
|
@ -97,14 +126,14 @@ const Login: React.FC<LoginProps> = ({ setUser }) => {
|
|||
name="email"
|
||||
placeholder="you@example.com"
|
||||
className="grow"
|
||||
value={formData.email}
|
||||
onChange={handleChange}
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
</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">
|
||||
<span className="label-text font-medium">Password</span>
|
||||
</label>
|
||||
|
|
@ -125,22 +154,51 @@ const Login: React.FC<LoginProps> = ({ setUser }) => {
|
|||
name="password"
|
||||
placeholder="Enter your password"
|
||||
className="grow"
|
||||
value={formData.password}
|
||||
onChange={handleChange}
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>}
|
||||
|
||||
<div className="form-control w-full px-5 mt-6">
|
||||
<button type="submit" className="btn btn-primary w-full">
|
||||
Sign In
|
||||
<div className="form-control w-90 px-5 mt-4">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="btn btn-primary w-full"
|
||||
>
|
||||
{loading
|
||||
? "Please wait..."
|
||||
: isForgotMode
|
||||
? "Send Reset Link"
|
||||
: "Login"}
|
||||
</button>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -1,36 +1,29 @@
|
|||
import React from "react";
|
||||
import { User, Mail, Lock, Edit2, Shield, CalendarDays } from "lucide-react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { User, Mail } from "lucide-react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { RootState } from "../../store";
|
||||
|
||||
interface UserProfile {
|
||||
interface User {
|
||||
id: string;
|
||||
name: 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 user = useSelector((state: RootState) => state.user.user);
|
||||
console.log(user , "Line 16")
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2 max-h-screen">
|
||||
<div className="flex flex-col gap-2 ">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-md font-bold flex items-center gap-2">
|
||||
Profile Settings
|
||||
</h2>
|
||||
<button className="btn btn-sm btn-primary gap-1">
|
||||
<Edit2 size={16} /> Edit Profile
|
||||
<button className="btn btn-sm btn-outline btn-primary">
|
||||
Change Password
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
@ -43,8 +36,7 @@ const Settings: React.FC = () => {
|
|||
<User size={60} />
|
||||
</div>
|
||||
</div>
|
||||
<h3 className="mt-3 text-lg font-semibold">{userData.name}</h3>
|
||||
<p className="text-sm text-base-content/70">{userData.role}</p>
|
||||
<h3 className="mt-3 text-lg font-semibold">{user?.name}</h3>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<Mail size={14} /> Email
|
||||
</p>
|
||||
<p className="text-sm font-medium">{userData.email}</p>
|
||||
<p className="text-sm font-medium">{user?.email}</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">
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ const ProjectLogsModal: React.FC<Props> = ({ isOpen, onClose, projectName, logs,
|
|||
{tab.title} <br/>
|
||||
{tab.appName}
|
||||
</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>
|
||||
</tr>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@
|
|||
import { configureStore } from "@reduxjs/toolkit";
|
||||
import windowReducer from "./windowSlice";
|
||||
import projectsReducer from "./projectSlice";
|
||||
import userReducer from "./userSlice";
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
window: windowReducer,
|
||||
projects: projectsReducer,
|
||||
user: userReducer,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -67,6 +67,10 @@ interface Window {
|
|||
electron: {
|
||||
login: (credentials: { email: string; password: string }) => Promise<any>;
|
||||
logout: () => Promise<any>;
|
||||
forgotPassword: (email: string) => Promise<{
|
||||
success: boolean;
|
||||
message: string;
|
||||
}>;
|
||||
getToken: (tokenKey: "ACCESS_TOKEN" | "REFRESH_TOKEN") => Promise<string | null>;
|
||||
uploadScreenshot: () => {};
|
||||
getActiveAppDetails: () => {};
|
||||
|
|
@ -94,7 +98,7 @@ interface Window {
|
|||
|
||||
// getProjectLogs: (projectId: string) => Promise<{ success: boolean; logs: ProjectLogResponse[] }>;
|
||||
getProjectLogs: (
|
||||
projectID: string,
|
||||
projectId: string,
|
||||
fromDate?: string,
|
||||
toDate?: string
|
||||
) => Promise<{ success: boolean; logs: ProjectLogResponse[] }>;
|
||||
|
|
|
|||
Loading…
Reference in New Issue