chnage password

This commit is contained in:
JaiPandey425 2025-11-14 12:50:18 +05:30
parent 8d2961c50d
commit b56a6e11c4
8 changed files with 487 additions and 108 deletions

View File

@ -58,15 +58,15 @@ export const registerIPCHandlers = (mainWindow: BrowserWindow) => {
ipcMain.handle("resize-window", (event, width: number, height: number) => { ipcMain.handle("resize-window", (event, width: number, height: number) => {
const mainWindow = getMainWindow(); const mainWindow = getMainWindow();
if (mainWindow && !mainWindow.isDestroyed()) { if (mainWindow && !mainWindow.isDestroyed()) {
console.log(`📏 Resizing window to ${width}x${height}`); console.log(`📏 Resizing window to ${width}x${height}`);
mainWindow.setResizable(true); // temporarily enable resizing mainWindow.setResizable(true); // temporarily enable resizing
mainWindow.setSize(width, height); mainWindow.setSize(width, height);
mainWindow.setResizable(false); // disable again if you want to lock it mainWindow.setResizable(false); // disable again if you want to lock it
} }
}); });
// ✅ Start background polling when main window is ready // ✅ Start background polling when main window is ready
@ -86,9 +86,9 @@ ipcMain.handle("get-project-list", async () => {
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);
return { return {
success: false, success: false,
message: error?.message || String(error) || "Unknown error" message: error?.message || String(error) || "Unknown error"
}; };
} }
}); });
@ -165,7 +165,7 @@ ipcMain.handle("tracking-tabs-opening-and-closing", async (event, payload) => {
// console.log("get-project-logs",projectID) // console.log("get-project-logs",projectID)
// const response = await api.get(`/report/project/${projectID}`); // const response = await api.get(`/report/project/${projectID}`);
// console.log("response-logs 164" , response) // console.log("response-logs 164" , response)
// return response; // return response;
// } catch (error) { // } catch (error) {
// console.error("Error fetching project logs:", error); // console.error("Error fetching project logs:", 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}`;
// 🧠 Append query params only if dates are provided // 🧠 Append query params only if dates are provided
if (fromDate && toDate) { if (fromDate && toDate) {
@ -191,7 +191,7 @@ ipcMain.handle("get-project-logs", async (event, { projectID, fromDate, toDate }
url += `?${params.toString()}`; url += `?${params.toString()}`;
} }
console.log("url" , url) 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);
@ -206,7 +206,7 @@ ipcMain.handle("get-project-logs", async (event, { projectID, fromDate, toDate }
ipcMain.handle("forgot-password", async (event, email: string) => { ipcMain.handle("forgot-password", async (event, email: string) => {
try { try {
const response = await api.post("/forgot-password", { email }); const response = await api.post("/forgot-password", { email });
console.log(response , "Line response") console.log(response, "Line response")
if (response) { if (response) {
return { success: true, message: response.data?.message }; return { success: true, message: response.data?.message };
} }
@ -218,3 +218,49 @@ ipcMain.handle("forgot-password", async (event, email: string) => {
}; };
} }
}); });
ipcMain.handle("get-profile", async () => {
try {
const response = await api.get("/profile");
console.log(response.data)
return {
success: true,
data: response.data,
};
} catch (error: any) {
console.error(" Profile fetch error:", error.response?.data || error.message);
return {
success: false,
message: error.response?.data?.message || "Something went wrong",
};
}
});
ipcMain.handle("change-password", async (event, data) => {
console.log("data-change-password", data)
// Build final payload
const payload = {
userDetails: {
oldPassword: data.oldPassword,
password: data.password,
cPassword: data.cPassword,
},
};
console.log("Change Password payload" , payload)
try {
const response = await api.patch("/profile", payload);
console.log("Chnage-passowrd-response", response)
return { success: true, data: response.data };
} catch (error: any) {
console.error("❌ Change password error:", error.message);
return {
success: false,
message: error.response?.data?.message || "Password update failed",
};
}
});

View File

@ -26,6 +26,11 @@ contextBridge.exposeInMainWorld('electron', {
toDate?: string toDate?: string
) => ipcRenderer.invoke("get-project-logs", { projectID, fromDate, toDate }), ) => ipcRenderer.invoke("get-project-logs", { projectID, fromDate, toDate }),
getProfile: () => ipcRenderer.invoke("get-profile"),
changePassword: (data: { oldPassword: string; password: string; cPassword: string }) =>
ipcRenderer.invoke("change-password", data),
} satisfies Window['electron']); } satisfies Window['electron']);
// function ipcInvoke<Key extends keyof EventPayloadMapping>( // function ipcInvoke<Key extends keyof EventPayloadMapping>(

View File

@ -29,6 +29,7 @@ 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"; import { clearUser } from "../store/userSlice";
import { setError, setLoading, setProfile } from "../store/profileSlice";
// interface User { // interface User {
// email: string; // email: string;
@ -116,15 +117,32 @@ const DashboardLayout = () => {
}); });
const [activeProjectLogId, setActiveProjectLogId] = useState("") const [activeProjectLogId, setActiveProjectLogId] = useState("")
const { finalizeTracking } = useActiveAppTracker( const { finalizeTracking } = useActiveAppTracker(
isTracking, isTracking,
activeProjectLogId, activeProjectLogId,
selectedProject?.projectId ?? null, selectedProject?.projectId ?? null,
selectedProject?.projectName ?? null selectedProject?.projectName ?? null
); );
const [projectsData, setProjectsData] = useState<Project[]>([]); const [projectsData, setProjectsData] = useState<Project[]>([]);
const fetchProfile = async () => {
try {
dispatch(setLoading(true));
const res = await window.electron.getProfile();
console.log("Profile", res)
if (res.success && res.data) {
dispatch(setProfile(res.data));
} else {
dispatch(setError("Failed to fetch profile"));
}
} catch (err: any) {
dispatch(setError(err.message));
} finally {
dispatch(setLoading(false));
}
};
// fetch the project list of VA // fetch the project list of VA
useEffect(() => { useEffect(() => {
const fetchProjects = async () => { const fetchProjects = async () => {
@ -177,7 +195,7 @@ const DashboardLayout = () => {
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:initialElapsedTime, elapsedTime: initialElapsedTime,
startTime: null, startTime: null,
endTime: null, endTime: null,
isTracking: false, isTracking: false,
@ -192,6 +210,8 @@ const DashboardLayout = () => {
// Set the initial elapsed time in the component state // Set the initial elapsed time in the component state
setElapsedTime(initialElapsedTime); setElapsedTime(initialElapsedTime);
} }
fetchProfile()
} else { } else {
console.warn("⚠️ Unexpected response:", res); console.warn("⚠️ Unexpected response:", res);
} }
@ -209,14 +229,14 @@ const DashboardLayout = () => {
const handleLogout = async () => { const handleLogout = async () => {
try { try {
const res = await window.electron.logout(); const res = await window.electron.logout();
if (res) { if (res) {
dispatch(clearUser()); // ✅ Removes from Redux + localStorage dispatch(clearUser()); // ✅ Removes from Redux + localStorage
navigate("/login"); navigate("/login");
}
} catch (error) {
console.error("Logout error:", error);
} }
} catch (error) {
console.error("Logout error:", error);
}
}; };
@ -441,51 +461,51 @@ const DashboardLayout = () => {
// Handle project selection // Handle project selection
const handleProjectSelect = (projectId: string) => { const handleProjectSelect = (projectId: string) => {
if (isTracking) return; // prevent switching while tracking 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) return; if (!project) return;
setSelectedProject(project); setSelectedProject(project);
dispatch(setSelectedProjectSlice(project)); dispatch(setSelectedProjectSlice(project));
const existingData = projectTrackingMap.get(projectId); const existingData = projectTrackingMap.get(projectId);
setProjectTrackingMap((prev) => { setProjectTrackingMap((prev) => {
const updated = new Map(prev); const updated = new Map(prev);
// If project is not yet tracked, initialize it with totalWorkMs // If project is not yet tracked, initialize it with totalWorkMs
if (!existingData) { if (!existingData) {
const initialElapsedTime = Math.floor((project.totalWorkMs || 0) / 1000); const initialElapsedTime = Math.floor((project.totalWorkMs || 0) / 1000);
updated.set(projectId, { updated.set(projectId, {
elapsedTime: initialElapsedTime, 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: initialElapsedTime, accumulatedTime: initialElapsedTime,
}); });
// Update component-level state // Update component-level state
setElapsedTime(initialElapsedTime); setElapsedTime(initialElapsedTime);
setStartTime(null); setStartTime(null);
setEndTime(null); setEndTime(null);
setIsTracking(false); setIsTracking(false);
setIsPaused(false); setIsPaused(false);
} else { } else {
// If already tracked, restore its state // If already tracked, restore its state
setElapsedTime(existingData.elapsedTime); setElapsedTime(existingData.elapsedTime);
setStartTime(existingData.startTime); setStartTime(existingData.startTime);
setEndTime(existingData.endTime); setEndTime(existingData.endTime);
setIsTracking(existingData.isTracking); setIsTracking(existingData.isTracking);
setIsPaused(existingData.isPaused); setIsPaused(existingData.isPaused);
} }
return updated; return updated;
}); });
}; };
const trackingState: TrackingState = { const trackingState: TrackingState = {
@ -593,7 +613,7 @@ const DashboardLayout = () => {
<div className={`flex ${!isTracking ? "max-h-screen min-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 , index) => ( {menuItems.map((item, index) => (
<div className="tooltip z-100 tooltip-right" data-tip={item.label} key={index}> <div className="tooltip z-100 tooltip-right" data-tip={item.label} key={index}>
<NavLink <NavLink
key={item.label} key={item.label}

View File

@ -0,0 +1,131 @@
// src/ui/pages/Settings/ChangePasswordModal.tsx
import React, { useState } from "react";
import { Eye, EyeOff } from "lucide-react";
interface ChangePasswordModalProps {
isOpen: boolean;
onClose: () => void;
}
const ChangePasswordModal: React.FC<ChangePasswordModalProps> = ({
isOpen,
onClose,
}) => {
const [oldPassword, setOldPassword] = useState("");
const [newPassword, setNewPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [loading, setLoading] = useState(false);
const [showPassword, setShowPassword] = useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (newPassword !== confirmPassword) {
alert("New passwords do not match!");
return;
}
setLoading(true);
try {
const payload = {
oldPassword,
password: newPassword,
cPassword: confirmPassword,
};
// 🔗 Call the Electron bridge function
const response = await window.electron.changePassword(payload);
if (response.success) {
alert("Password changed successfully!");
onClose();
} else {
alert(response.message || "Failed to change password.");
}
} catch (error) {
console.error("Password change error:", error);
alert("Error changing password.");
} finally {
setLoading(false);
}
};
if (!isOpen) return null;
return (
<dialog open className="modal modal-open">
<div className="modal-box w-96">
<h3 className="font-bold text-lg mb-3">Change Password</h3>
<form onSubmit={handleSubmit} className="space-y-3">
{/* Old Password */}
<div>
<label className="label text-xs font-medium">Current Password</label>
<input
type={showPassword ? "text" : "password"}
className="input input-bordered w-full"
value={oldPassword}
onChange={(e) => setOldPassword(e.target.value)}
required
/>
</div>
{/* New Password */}
<div>
<label className="label text-xs font-medium">New Password</label>
<div className="relative">
<input
type={showPassword ? "text" : "password"}
className="input input-bordered w-full pr-10"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
required
/>
<button
type="button"
className="absolute right-2 top-2 text-gray-500"
onClick={() => setShowPassword(!showPassword)}
>
{showPassword ? <EyeOff size={16} /> : <Eye size={16} />}
</button>
</div>
</div>
{/* Confirm Password */}
<div>
<label className="label text-xs font-medium">Confirm New Password</label>
<input
type={showPassword ? "text" : "password"}
className="input input-bordered w-full"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
required
/>
</div>
<div className="modal-action">
<button
type="button"
className="btn btn-sm btn-ghost"
onClick={onClose}
>
Cancel
</button>
<button
type="submit"
disabled={loading}
className={`btn btn-sm btn-primary ${loading ? "loading" : ""}`}
>
{loading ? "Updating..." : "Update"}
</button>
</div>
</form>
</div>
<form method="dialog" className="modal-backdrop" onClick={onClose}>
<button>close</button>
</form>
</dialog>
);
};
export default ChangePasswordModal;

View File

@ -1,56 +1,135 @@
import React, { useEffect, useState } from "react";
import { User, Mail } from "lucide-react"; import React, { useState } from "react";
import { User, Mail, Phone, Calendar, MapPin, CreditCard, Globe, Lock } from "lucide-react";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { RootState } from "../../store"; import { RootState } from "../../store";
import ChangePasswordModal from "./ChangePasswordModal";
interface User {
id: string;
name: string;
email: string;
}
const Settings: React.FC = () => { const Settings: React.FC = () => {
const { profile } = useSelector((state: RootState) => state.profile);
const [modalOpen, setModalOpen] = useState(false);
const fullName = `${profile?.firstName || ""} ${profile?.lastName || ""}`.trim();
const user = useSelector((state: RootState) => state.user.user);
console.log(user , "Line 16")
return ( return (
<div className="flex flex-col gap-2 "> <div className="flex flex-col gap-3">
{/* 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 <User size={18} /> Profile Settings
</h2> </h2>
<button className="btn btn-sm btn-outline btn-primary"> <button
Change Password onClick={() => setModalOpen(true)}
className="btn btn-sm btn-outline btn-primary flex items-center gap-2"
>
<Lock size={14} /> Change Password
</button> </button>
</div> </div>
{/* Profile Section */} {/* Profile Card */}
<div className=" card bg-base-100 border border-base-300 shadow-sm p-4 flex flex-col md:flex-row gap-4"> <div className="card bg-base-100 border border-base-300 shadow-sm p-4">
{/* Profile Picture */} <div className="flex flex-col md:flex-row gap-4">
<div className="flex flex-col items-center md:w-1/3"> {/* Compact Avatar Section */}
<div className="avatar"> <div className="flex flex-col items-center md:items-start md:w-48 shrink-0">
<div className="w-28 rounded-full ring ring-primary ring-offset-base-100 ring-offset-2 flex justify-center items-center"> <div className="avatar">
<User size={60} /> <div className="w-20 rounded-full ring ring-primary ring-offset-base-100 ring-offset-1 flex justify-center items-center bg-base-200">
<User size={40} className="text-base-content/50" />
</div>
</div>
<div className="mt-2 text-center md:text-left">
<h3 className="text-sm font-semibold">{fullName || "N/A"}</h3>
<p className="text-xs text-base-content/60 flex items-center gap-1 justify-center md:justify-start mt-0.5">
<Mail size={10} /> {profile?.email || "No Email"}
</p>
</div> </div>
</div> </div>
<h3 className="mt-3 text-lg font-semibold">{user?.name}</h3>
</div>
<div className="divider md:divider-horizontal" /> <div className="divider md:divider-horizontal my-0 md:mx-2" />
{/* Details Section */} {/* Compact Details Grid */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 flex-1"> <div className="flex-1 grid grid-cols-2 sm:grid-cols-3 gap-2.5">
<div className="p-2 rounded-md bg-base-200/50"> {/* Basic Info */}
<p className="text-xs text-base-content/70 flex items-center gap-2"> <div className="p-2 rounded bg-base-200/40">
<Mail size={14} /> Email <p className="text-xs text-base-content/60 flex items-center gap-1 mb-0.5">
</p> <Calendar size={11} /> DOB
<p className="text-sm font-medium">{user?.email}</p> </p>
<p className="text-xs font-medium">{profile?.dob || "N/A"}</p>
</div>
<div className="p-2 rounded bg-base-200/40">
<p className="text-xs text-base-content/60 flex items-center gap-1 mb-0.5">
🎂 Age
</p>
<p className="text-xs font-medium">{profile?.age || "N/A"}</p>
</div>
{/* Contact Info */}
<div className="p-2 rounded bg-base-200/40">
<p className="text-xs text-base-content/60 flex items-center gap-1 mb-0.5">
<Phone size={11} /> Phone
</p>
<p className="text-xs font-medium truncate" title={profile?.phone || "N/A"}>
{profile?.phone || "N/A"}
</p>
</div>
<div className="p-2 rounded bg-base-200/40">
<p className="text-xs text-base-content/60 flex items-center gap-1 mb-0.5">
💬 Skype
</p>
<p className="text-xs font-medium truncate" title={profile?.skype || "Not Linked"}>
{profile?.skype || "Not Linked"}
</p>
</div>
{/* Payment Info */}
<div className="p-2 rounded bg-base-200/40">
<p className="text-xs text-base-content/60 flex items-center gap-1 mb-0.5">
<CreditCard size={11} /> Payment
</p>
<p className="text-xs font-medium capitalize truncate">
{profile?.paymentMethod || "N/A"}
</p>
</div>
{profile?.paymentMethod === "payoneer" && (
<div className="p-2 rounded bg-base-200/40">
<p className="text-xs text-base-content/60 flex items-center gap-1 mb-0.5">
💳 Payoneer
</p>
<p className="text-xs font-medium truncate" title={profile?.payoneerAccount || "N/A"}>
{profile?.payoneerAccount || "N/A"}
</p>
</div>
)}
{profile?.paymentMethod === "veem" && (
<div className="p-2 rounded bg-base-200/40">
<p className="text-xs text-base-content/60 flex items-center gap-1 mb-0.5">
🏦 Veem
</p>
<p className="text-xs font-medium truncate" title={profile?.veemAccount || "N/A"}>
{profile?.veemAccount || "N/A"}
</p>
</div>
)}
{/* Location - Full Width */}
<div className="p-2 rounded bg-base-200/40 col-span-2 sm:col-span-3">
<p className="text-xs text-base-content/60 flex items-center gap-1 mb-0.5">
<MapPin size={11} /> Address
</p>
<p className="text-xs font-medium line-clamp-2">
{[profile?.address, profile?.city, profile?.state, profile?.country]
.filter(Boolean)
.join(", ") || "N/A"}
</p>
</div>
</div> </div>
</div> </div>
</div> </div>
{/* Change Password Modal */}
<ChangePasswordModal isOpen={modalOpen} onClose={() => setModalOpen(false)} />
</div> </div>
); );
}; };

View File

@ -3,12 +3,14 @@ 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"; import userReducer from "./userSlice";
import profileReducer from "./profileSlice";
export const store = configureStore({ export const store = configureStore({
reducer: { reducer: {
window: windowReducer, window: windowReducer,
projects: projectsReducer, projects: projectsReducer,
user: userReducer, user: userReducer,
profile: profileReducer,
}, },
}); });

View File

@ -0,0 +1,60 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
interface ProfileData {
firstName: string;
lastName: string;
email: string;
dob: string;
age: string;
address: string;
bankName: string;
accountHolderName: string;
accountNumber: string;
routingNumber: string;
phone:string;
skype:string;
city:string;
state:string;
country:string;
paymentMethod:string;
payoneerAccount:string;
veemAccount:string;
_id: string;
}
interface ProfileState {
profile: ProfileData | null;
loading: boolean;
error: string | null;
}
const initialState: ProfileState = {
profile: null,
loading: false,
error: null,
};
const profileSlice = createSlice({
name: "profile",
initialState,
reducers: {
setLoading: (state, action: PayloadAction<boolean>) => {
state.loading = action.payload;
},
setProfile: (state, action: PayloadAction<ProfileData>) => {
state.profile = action.payload;
state.error = null;
},
setError: (state, action: PayloadAction<string | null>) => {
state.error = action.payload;
},
clearProfile: (state) => {
state.profile = null;
state.error = null;
state.loading = false;
},
},
});
export const { setLoading, setProfile, setError, clearProfile } = profileSlice.actions;
export default profileSlice.reducer;

56
types.d.ts vendored
View File

@ -63,6 +63,28 @@ interface ProjectLogResponse {
} }
interface ProfileData {
firstName: string;
lastName: string;
email: string;
dob: string;
age: string;
address: string;
bankName: string;
accountHolderName: string;
accountNumber: string;
routingNumber: string;
phone:string;
skype:string;
city:string;
state:string;
country:string;
paymentMethod:string;
payoneerAccount:string;
veemAccount:string;
_id: string;
}
interface Window { interface Window {
electron: { electron: {
login: (credentials: { email: string; password: string }) => Promise<any>; login: (credentials: { email: string; password: string }) => Promise<any>;
@ -96,16 +118,30 @@ interface Window {
appName: string; appName: string;
}) => Promise<any>; }) => Promise<any>;
getProjectLogs: ( getProjectLogs: (
projectId: string, projectId: string,
fromDate?: string, fromDate?: string,
toDate?: string toDate?: string
) => Promise<{ ) => Promise<{
success: boolean; success: boolean;
data: any; data: any;
message?: string; message?: string;
} }
>; >;
getProfile: () => Promise<{
success: boolean;
data?: ProfileData;
}>;
changePassword: (data: {
oldPassword: string;
password: string;
cPassword: string;
}) => Promise<{ success: boolean; message?: string; data?: any }>;
}; };
} }