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

@ -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
) => 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<Key extends keyof EventPayloadMapping>(

View File

@ -29,6 +29,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;
@ -125,6 +126,23 @@ const DashboardLayout = () => {
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
useEffect(() => {
const fetchProjects = async () => {
@ -192,6 +210,8 @@ const DashboardLayout = () => {
// Set the initial elapsed time in the component state
setElapsedTime(initialElapsedTime);
}
fetchProfile()
} else {
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 { 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 user = useSelector((state: RootState) => state.user.user);
console.log(user , "Line 16")
const fullName = `${profile?.firstName || ""} ${profile?.lastName || ""}`.trim();
return (
<div className="flex flex-col gap-2 ">
<div className="flex flex-col gap-3">
{/* Header */}
<div className="flex items-center justify-between">
<h2 className="text-md font-bold flex items-center gap-2">
Profile Settings
<User size={18} /> Profile Settings
</h2>
<button className="btn btn-sm btn-outline btn-primary">
Change Password
<button
onClick={() => setModalOpen(true)}
className="btn btn-sm btn-outline btn-primary flex items-center gap-2"
>
<Lock size={14} /> Change Password
</button>
</div>
{/* Profile Section */}
<div className=" card bg-base-100 border border-base-300 shadow-sm p-4 flex flex-col md:flex-row gap-4">
{/* Profile Picture */}
<div className="flex flex-col items-center md:w-1/3">
{/* Profile Card */}
<div className="card bg-base-100 border border-base-300 shadow-sm p-4">
<div className="flex flex-col md:flex-row gap-4">
{/* Compact Avatar Section */}
<div className="flex flex-col items-center md:items-start md:w-48 shrink-0">
<div className="avatar">
<div className="w-28 rounded-full ring ring-primary ring-offset-base-100 ring-offset-2 flex justify-center items-center">
<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>
<h3 className="mt-3 text-lg font-semibold">{user?.name}</h3>
</div>
<div className="divider md:divider-horizontal" />
{/* Details Section */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 flex-1">
<div className="p-2 rounded-md bg-base-200/50">
<p className="text-xs text-base-content/70 flex items-center gap-2">
<Mail size={14} /> Email
<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 className="divider md:divider-horizontal my-0 md:mx-2" />
{/* 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 className="text-sm font-medium">{user?.email}</p>
</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 projectsReducer from "./projectSlice";
import userReducer from "./userSlice";
import profileReducer from "./profileSlice";
export const store = configureStore({
reducer: {
window: windowReducer,
projects: projectsReducer,
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 {
electron: {
login: (credentials: { email: string; password: string }) => Promise<any>;
@ -107,6 +129,20 @@ interface Window {
}
>;
getProfile: () => Promise<{
success: boolean;
data?: ProfileData;
}>;
changePassword: (data: {
oldPassword: string;
password: string;
cPassword: string;
}) => Promise<{ success: boolean; message?: string; data?: any }>;
};
}