This commit is contained in:
BhagwanSingh 2025-11-14 13:03:27 +05:30
commit f7e77f895c
8 changed files with 470 additions and 91 deletions

View File

@ -225,3 +225,49 @@ ipcMain.handle('capture-specific-window', async (event, appTitle: string) => {
console.log(appTitle, "IPC HANDLE") console.log(appTitle, "IPC HANDLE")
return await captureSpecificApp(appTitle) 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",
};
}
});

View File

@ -28,6 +28,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

@ -31,6 +31,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;
@ -132,6 +133,23 @@ const DashboardLayout = () => {
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 () => {
@ -199,6 +217,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);
} }

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,57 +1,136 @@
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 user = useSelector((state: RootState) => state.user.user); const fullName = `${profile?.firstName || ""} ${profile?.lastName || ""}`.trim();
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="flex flex-col items-center md:items-start md:w-48 shrink-0">
<div className="avatar"> <div className="avatar">
<div className="w-28 rounded-full ring ring-primary ring-offset-base-100 ring-offset-2 flex justify-center items-center"> <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={60} /> <User size={40} className="text-base-content/50" />
</div> </div>
</div> </div>
<h3 className="mt-3 text-lg font-semibold">{user?.name}</h3> <div className="mt-2 text-center md:text-left">
</div> <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">
<div className="divider md:divider-horizontal" /> <Mail size={10} /> {profile?.email || "No Email"}
</p>
{/* Details Section */} </div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 flex-1"> </div>
<div className="p-2 rounded-md bg-base-200/50">
<p className="text-xs text-base-content/70 flex items-center gap-2"> <div className="divider md:divider-horizontal my-0 md:mx-2" />
<Mail size={14} /> Email
{/* Compact Details Grid */}
<div className="flex-1 grid grid-cols-2 sm:grid-cols-3 gap-2.5">
{/* Basic 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">
<Calendar size={11} /> DOB
</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> </p>
<p className="text-sm font-medium">{user?.email}</p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{/* Change Password Modal */}
<ChangePasswordModal isOpen={modalOpen} onClose={() => setModalOpen(false)} />
</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;

36
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>;
@ -109,6 +131,20 @@ interface Window {
} }
>; >;
getProfile: () => Promise<{
success: boolean;
data?: ProfileData;
}>;
changePassword: (data: {
oldPassword: string;
password: string;
cPassword: string;
}) => Promise<{ success: boolean; message?: string; data?: any }>;
}; };
} }