Merge branch 'master' of http://git.hicglobalsolutions.in/BhawanSingh/va-tracker
This commit is contained in:
commit
f7e77f895c
|
|
@ -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",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -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>(
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -118,7 +119,7 @@ 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,
|
||||||
|
|
@ -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 () => {
|
||||||
|
|
@ -184,7 +202,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,
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
@ -216,14 +236,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);
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -523,51 +543,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 = {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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 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="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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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>;
|
||||||
|
|
@ -98,16 +120,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 }>;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue