diff --git a/src/electron/ipc/ipcHandler.ts b/src/electron/ipc/ipcHandler.ts index 4dbccfb..0d365f8 100644 --- a/src/electron/ipc/ipcHandler.ts +++ b/src/electron/ipc/ipcHandler.ts @@ -225,3 +225,49 @@ ipcMain.handle('capture-specific-window', async (event, appTitle: string) => { console.log(appTitle, "IPC HANDLE") return await captureSpecificApp(appTitle) }); + + +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", + }; + } +}); diff --git a/src/electron/preload.cts b/src/electron/preload.cts index 881d0aa..fb72e80 100644 --- a/src/electron/preload.cts +++ b/src/electron/preload.cts @@ -28,6 +28,11 @@ contextBridge.exposeInMainWorld('electron', { toDate?: string ) => 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']); // function ipcInvoke( diff --git a/src/ui/layout/DashboardLayout.tsx b/src/ui/layout/DashboardLayout.tsx index e0ab89c..79656a5 100644 --- a/src/ui/layout/DashboardLayout.tsx +++ b/src/ui/layout/DashboardLayout.tsx @@ -31,6 +31,7 @@ import { useDispatch, useSelector } from "react-redux"; import { RootState } from "../store"; import { setProjectsDataSlice, setProjectSummarySlice, setSelectedProjectSlice } from "../store/projectSlice"; import { clearUser } from "../store/userSlice"; +import { setError, setLoading, setProfile } from "../store/profileSlice"; // interface User { // email: string; @@ -118,11 +119,11 @@ const DashboardLayout = () => { }); const [activeProjectLogId, setActiveProjectLogId] = useState("") - const { finalizeTracking } = useActiveAppTracker( + const { finalizeTracking } = useActiveAppTracker( isTracking, activeProjectLogId, selectedProject?.projectId ?? null, - selectedProject?.projectName ?? null + selectedProject?.projectName ?? null ); const socketRef = useRef(null); @@ -132,6 +133,23 @@ const DashboardLayout = () => { const [projectsData, setProjectsData] = useState([]); + 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 useEffect(() => { const fetchProjects = async () => { @@ -184,7 +202,7 @@ const DashboardLayout = () => { const updated = new Map(prev); if (!updated.has(firstProject.projectId)) { updated.set(firstProject.projectId, { - elapsedTime:initialElapsedTime, + elapsedTime: initialElapsedTime, startTime: null, endTime: null, isTracking: false, @@ -199,6 +217,8 @@ const DashboardLayout = () => { // Set the initial elapsed time in the component state setElapsedTime(initialElapsedTime); } + fetchProfile() + } else { console.warn("⚠️ Unexpected response:", res); } @@ -216,14 +236,14 @@ const DashboardLayout = () => { const handleLogout = async () => { try { - const res = await window.electron.logout(); - if (res) { - dispatch(clearUser()); // ✅ Removes from Redux + localStorage - navigate("/login"); + const res = await window.electron.logout(); + if (res) { + dispatch(clearUser()); // ✅ Removes from Redux + localStorage + navigate("/login"); + } + } catch (error) { + console.error("Logout error:", error); } - } catch (error) { - console.error("Logout error:", error); - } }; @@ -523,51 +543,51 @@ const DashboardLayout = () => { // Handle project selection 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); - if (!project) return; + const project = projectsData.find((p) => p.projectId === projectId); + if (!project) return; - setSelectedProject(project); - dispatch(setSelectedProjectSlice(project)); + setSelectedProject(project); + dispatch(setSelectedProjectSlice(project)); - const existingData = projectTrackingMap.get(projectId); + const existingData = projectTrackingMap.get(projectId); - setProjectTrackingMap((prev) => { - const updated = new Map(prev); + 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, - }); + // 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); - } + // 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; - }); -}; + return updated; + }); + }; const trackingState: TrackingState = { diff --git a/src/ui/pages/Settings/ChangePasswordModal.tsx b/src/ui/pages/Settings/ChangePasswordModal.tsx new file mode 100644 index 0000000..cc08d41 --- /dev/null +++ b/src/ui/pages/Settings/ChangePasswordModal.tsx @@ -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 = ({ + 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 ( + +
+

Change Password

+ +
+ {/* Old Password */} +
+ + setOldPassword(e.target.value)} + required + /> +
+ + {/* New Password */} +
+ +
+ setNewPassword(e.target.value)} + required + /> + +
+
+ + {/* Confirm Password */} +
+ + setConfirmPassword(e.target.value)} + required + /> +
+ +
+ + +
+
+
+ +
+ +
+
+ ); +}; + +export default ChangePasswordModal; diff --git a/src/ui/pages/Settings/Settings.tsx b/src/ui/pages/Settings/Settings.tsx index 28eff34..5d024bd 100644 --- a/src/ui/pages/Settings/Settings.tsx +++ b/src/ui/pages/Settings/Settings.tsx @@ -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 { RootState } from "../../store"; - -interface User { - id: string; - name: string; - email: string; -} - +import ChangePasswordModal from "./ChangePasswordModal"; 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 ( -
+
{/* Header */}

- Profile Settings + Profile Settings

-
- {/* Profile Section */} -
- {/* Profile Picture */} -
-
-
- + {/* Profile Card */} +
+
+ {/* Compact Avatar Section */} +
+
+
+ +
+
+
+

{fullName || "N/A"}

+

+ {profile?.email || "No Email"} +

-

{user?.name}

-
-
+
- {/* Details Section */} -
-
-

- Email -

-

{user?.email}

+ {/* Compact Details Grid */} +
+ {/* Basic Info */} +
+

+ DOB +

+

{profile?.dob || "N/A"}

+
+ +
+

+ 🎂 Age +

+

{profile?.age || "N/A"}

+
+ + {/* Contact Info */} +
+

+ Phone +

+

+ {profile?.phone || "N/A"} +

+
+ +
+

+ 💬 Skype +

+

+ {profile?.skype || "Not Linked"} +

+
+ + {/* Payment Info */} +
+

+ Payment +

+

+ {profile?.paymentMethod || "N/A"} +

+
+ + {profile?.paymentMethod === "payoneer" && ( +
+

+ 💳 Payoneer +

+

+ {profile?.payoneerAccount || "N/A"} +

+
+ )} + + {profile?.paymentMethod === "veem" && ( +
+

+ 🏦 Veem +

+

+ {profile?.veemAccount || "N/A"} +

+
+ )} + + {/* Location - Full Width */} +
+

+ Address +

+

+ {[profile?.address, profile?.city, profile?.state, profile?.country] + .filter(Boolean) + .join(", ") || "N/A"} +

+
+ {/* Change Password Modal */} + setModalOpen(false)} />
); }; diff --git a/src/ui/store/index.tsx b/src/ui/store/index.tsx index a07039a..18dc804 100644 --- a/src/ui/store/index.tsx +++ b/src/ui/store/index.tsx @@ -3,12 +3,14 @@ import { configureStore } from "@reduxjs/toolkit"; import windowReducer from "./windowSlice"; import projectsReducer from "./projectSlice"; import userReducer from "./userSlice"; +import profileReducer from "./profileSlice"; export const store = configureStore({ reducer: { window: windowReducer, projects: projectsReducer, user: userReducer, + profile: profileReducer, }, }); diff --git a/src/ui/store/profileSlice.tsx b/src/ui/store/profileSlice.tsx new file mode 100644 index 0000000..eca87d4 --- /dev/null +++ b/src/ui/store/profileSlice.tsx @@ -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) => { + state.loading = action.payload; + }, + setProfile: (state, action: PayloadAction) => { + state.profile = action.payload; + state.error = null; + }, + setError: (state, action: PayloadAction) => { + 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; diff --git a/types.d.ts b/types.d.ts index 08bbcd0..012ad32 100644 --- a/types.d.ts +++ b/types.d.ts @@ -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 { electron: { login: (credentials: { email: string; password: string }) => Promise; @@ -98,16 +120,30 @@ interface Window { appName: string; }) => Promise; - getProjectLogs: ( - projectId: string, - fromDate?: string, - toDate?: string -) => Promise<{ - success: boolean; - data: any; - message?: string; -} ->; + getProjectLogs: ( + projectId: string, + fromDate?: string, + toDate?: string + ) => Promise<{ + success: boolean; + data: any; + message?: string; + } + >; + + getProfile: () => Promise<{ + success: boolean; + data?: ProfileData; + }>; + + + changePassword: (data: { + oldPassword: string; + password: string; + cPassword: string; + }) => Promise<{ success: boolean; message?: string; data?: any }>; + + }; }