diff --git a/.env b/.env new file mode 100644 index 0000000..e69de29 diff --git a/resources/wireguard/windows/wg0.conf b/resources/wireguard/windows/wg0.conf index 5053efb..69a3e03 100644 --- a/resources/wireguard/windows/wg0.conf +++ b/resources/wireguard/windows/wg0.conf @@ -1,10 +1,10 @@ [Interface] PrivateKey = CEzCd25ufxbOjvRhMqLfkcdP/OziG5i6OkXCfMZrI1I= -Address = 10.0.0.2/24 +Address = 10.0.0.2/32 DNS = 1.1.1.1 [Peer] PublicKey = jG+7H5thEiNjxY1XqI1R9oyMAMTIegLwE3tZkS1Rj2U= Endpoint = 35.95.209.161:51820 -AllowedIPs = 10.0.0.0/24 +AllowedIPs = 10.0.0.0/32 0.0.0.0/0 PersistentKeepalive = 25 diff --git a/src/electron/api/axiosClient.ts b/src/electron/api/axiosClient.ts index 95d6d3e..1ba36c5 100644 --- a/src/electron/api/axiosClient.ts +++ b/src/electron/api/axiosClient.ts @@ -69,4 +69,4 @@ api.interceptors.response.use( } ); -export default api; +export default api; \ No newline at end of file diff --git a/src/electron/api/baseURL.ts b/src/electron/api/baseURL.ts new file mode 100644 index 0000000..f380736 --- /dev/null +++ b/src/electron/api/baseURL.ts @@ -0,0 +1,6 @@ +let CURRENT_BASE_URL = "https://quality-friendly-goldfish.ngrok-free.app/api/v1/va"; + +export const getBaseURL = () => CURRENT_BASE_URL; +export const setBaseURL = (url: string) => { + CURRENT_BASE_URL = url; +}; diff --git a/src/electron/api/regionCheck.ts b/src/electron/api/regionCheck.ts new file mode 100644 index 0000000..b545c8f --- /dev/null +++ b/src/electron/api/regionCheck.ts @@ -0,0 +1,40 @@ + +import axios from "axios"; +import https from "https"; +import { getBaseURL } from "./baseURL.js"; + +const httpsAgent = new https.Agent({ rejectUnauthorized: false }); + +export const checkRegion = async () => { + const baseURL = await getBaseURL(); + + console.log("checkRegion@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" ,baseURL) + + try { + + const res = await axios.get(`${baseURL}/health`, { + headers: { "ngrok-skip-browser-warning": "true" }, + httpsAgent + }); + + console.log(res , "checkRegion") + + return { + success: true, + status: res.status, + data: res.data + }; + + } catch (error: any) { + console.log(error , "Line 29") + const status = error?.response?.status || 500; + + console.log("Region Check Error:", status); + + return { + success: false, + status, + message: "Region check failed" + }; + } +}; diff --git a/src/electron/ipc/ipcHandler.ts b/src/electron/ipc/ipcHandler.ts index 5ec569b..d88f698 100644 --- a/src/electron/ipc/ipcHandler.ts +++ b/src/electron/ipc/ipcHandler.ts @@ -3,7 +3,9 @@ import { getToken, saveToken, clearToken } from "../utils/tokenHelper.js"; import { getActiveApp, handleLogin, takeScreenshot, startWindowPolling, getRunningAppsList, captureSpecificApp } from "../utils/utilityFunctions.js"; import api from "../api/axiosClient.js"; import { getMainWindow } from '../main.js'; +import { checkRegion } from "../api/regionCheck.js"; import { startVPN, stopVPN } from "../vpn.js"; +import { getBaseURL, setBaseURL } from "../api/baseURL.js"; @@ -11,6 +13,20 @@ import { startVPN, stopVPN } from "../vpn.js"; export const registerIPCHandlers = (mainWindow: BrowserWindow) => { console.log("⚙️ Registering IPC Handlers..."); + + ipcMain.handle("GET_BASE_URL", () => { + return getBaseURL(); +}); + +// UPDATE Base URL +ipcMain.handle("UPDATE_BASE_URL", (event, url: string) => { + setBaseURL(url); + return { + updated: true, + baseURL: url + }; +}); + ipcMain.handle("get-token", async (_, key: "ACCESS_TOKEN" | "REFRESH_TOKEN") => { return await getToken(key); }); @@ -535,6 +551,11 @@ ipcMain.handle("update-payment", async (event, data: Record) => { +ipcMain.handle("check-region", async () => { + return await checkRegion(); +}); + + export const sendTokenExpired = () => { console.log("⚠️ Sending token-expired event to renderer"); diff --git a/src/electron/preload.cts b/src/electron/preload.cts index 1de0eea..2864150 100644 --- a/src/electron/preload.cts +++ b/src/electron/preload.cts @@ -65,9 +65,14 @@ contextBridge.exposeInMainWorld('electron', { getLeaveList: () => ipcRenderer.invoke("get-leave-list"), getAdjustmentsList:(vaId: string) => ipcRenderer.invoke("get-adjustments-list",vaId), + checkRegion: () => ipcRenderer.invoke("check-region"), + connectVPN: () => ipcRenderer.invoke("vpn-connect"), disconnectVPN: () => ipcRenderer.invoke("vpn-disconnect"), + getBaseURL: () => ipcRenderer.invoke("GET_BASE_URL"), + updateBaseURL: (url: string) => ipcRenderer.invoke("UPDATE_BASE_URL", url), + } satisfies Window['electron']); // function ipcInvoke( diff --git a/src/electron/utils/utilityFunctions.ts b/src/electron/utils/utilityFunctions.ts index c09a7ab..87e8175 100644 --- a/src/electron/utils/utilityFunctions.ts +++ b/src/electron/utils/utilityFunctions.ts @@ -8,6 +8,7 @@ import { saveToken } from "./tokenHelper.js"; import { activeWindow } from "get-windows"; import { exec } from "child_process"; import { getUIPath } from "../pathResolver.js"; +import { getBaseURL } from "../api/baseURL.js"; const httpsAgent = new https.Agent({ rejectUnauthorized: false @@ -61,14 +62,16 @@ export const handleLogin = async (credentials: { email: string, password: string // Call your login API console.log("🟡 login Request received", credentials); + let baseURL = getBaseURL() + console.log("app.getAppPath()", app.getAppPath()); console.log("UIPATH", getUIPath()); - console.log("Doom 1"); + console.log("Doom 1" , `${baseURL}/api/v1/va/login`); try { console.log("➡️ Calling API..."); const response = await axios.post( - 'https://quality-friendly-goldfish.ngrok-free.app/api/v1/va/login', + `https://quality-friendly-goldfish.ngrok-free.app/api/v1/va/login`, { userDetails: { ...credentials } }, { headers: { "ngrok-skip-browser-warning": 69420 }, httpsAgent } ); diff --git a/src/ui/App.tsx b/src/ui/App.tsx index 7de1c12..9852676 100644 --- a/src/ui/App.tsx +++ b/src/ui/App.tsx @@ -97,22 +97,23 @@ import Vpn from "./pages/Vpn/Vpn"; import Schedule from "./pages/Schedule/Schedule"; import PaysSlips from "./pages/PaySlips/PaysSlips"; import ApplyLeaveAndAdjustmentsTime from "./pages/LeaveAndAdjustments/ApplyLeaveAndAdjustmentsTime"; +import VpnGuard from "./pages/Vpn/VpnGuard"; const App: React.FC = () => { - + const dispatch = useDispatch(); // ✅ Get user directly from Redux const user = useSelector((state: RootState) => state.user.user); - + useEffect(() => { dispatch(loadUserFromStorage()); }, [dispatch]); - console.log("App.tsx → user:", user); + console.log("App.tsx → user:", user); return ( @@ -122,7 +123,9 @@ const App: React.FC = () => { path="/login" element={ - + {/* */} + + {/* */} } /> @@ -132,20 +135,22 @@ const App: React.FC = () => { path="/dashboard" element={ + {/* */} + {/* */} } > } /> } /> } /> - } /> + } /> } /> } /> } /> } /> - } /> - } /> + } /> + } /> {/* Default Redirect */} diff --git a/src/ui/layout/DashboardLayout.tsx b/src/ui/layout/DashboardLayout.tsx index 929ec0a..c071a1c 100644 --- a/src/ui/layout/DashboardLayout.tsx +++ b/src/ui/layout/DashboardLayout.tsx @@ -167,6 +167,8 @@ const DashboardLayout = () => { `Timer will continue running. Please stop manually when ready.` ); + + console.log("✅ Alert shown, timer continues running"); }, [selectedProject]); @@ -724,15 +726,26 @@ const DashboardLayout = () => { // return `${hrs}h ${mins}m `; }; + + const [checkIp, setCheckIp] = useState(false) + useEffect(() => { + setTimeout(() => { + fetch('https://api.ipify.org/?format=json').then((res) => console.log("IPPPPPPPPPPPPPP", res)) + }, 5000) + }, [checkIp]) + + return ( <>
{/* @@ -741,6 +754,7 @@ const DashboardLayout = () => { onClick={() => { window.electron.disconnectVPN(); }} + className="text-red-400" > Disconnect VPN */} diff --git a/src/ui/pages/Dashboard.tsx b/src/ui/pages/Dashboard.tsx index 8130cbe..46c89f5 100644 --- a/src/ui/pages/Dashboard.tsx +++ b/src/ui/pages/Dashboard.tsx @@ -106,6 +106,7 @@ const Dashboard: React.FC = () => { const projectSummary = useSelector((state: any) => state.projects.projectSummary); const projectTrackingMap = useSelector((state: any) => state.projects.projectTrackingMap); const [isRefreshing, setIsRefreshing] = useState(false); + const isLimitReached = selectedProject ? isProjectLimitReached(selectedProject) : false; console.log("Line 111", projectsData, selectedProject, projectTrackingMap) @@ -129,14 +130,14 @@ const Dashboard: React.FC = () => { }; const fetchProjects = async () => { - setIsRefreshing(true) + setIsRefreshing(true); try { console.log("📡 Fetching projects...Dashboard"); const res = await window.electron.getProjectList(); - console.log(" Project list response in dashboard: ", res.data?.[0]); + console.log("Project list response in dashboard: ", res.data?.[0]); if (res?.success && Array.isArray(res.data)) { - // Store data redux store + // Store data in redux store dispatch(setProjectsDataSlice(res.data)); dispatch( @@ -151,35 +152,67 @@ const Dashboard: React.FC = () => { }) ); - // Auto-select first project - if (res.data.length > 0) { - const firstProject = res.data[0]; + // Check if we already have a selected project that's being tracked + const currentlySelected = selectedProject; + const currentlyTracking = isTracking; + // Find if the currently selected project still exists in the fresh data + let projectToSelect = null; - console.log("Line 158" , firstProject) - // Select the project - dispatch(setSelectedProjectSlice(firstProject)); - - const initialElapsed = parseTimeStringToSeconds(firstProject.totalWorkTime); - - // Initialize tracking data for first project - dispatch( - initializeProjectTracking({ - projectId: firstProject.projectId, - initialTime: initialElapsed, - }) + if (currentlySelected && currentlyTracking) { + // We're tracking a project - try to preserve it + const existingProject = res.data.find( + (p: Project) => p.projectId === currentlySelected.projectId ); + + if (existingProject) { + projectToSelect = existingProject; + } } - fetchProfile() + // If no project is selected OR tracked project no longer exists, auto-select first project + if (!projectToSelect && res.data.length > 0) { + projectToSelect = res.data[0]; + } + if (projectToSelect) { + console.log("Line 158", projectToSelect); + // Select the project + dispatch(setSelectedProjectSlice(projectToSelect)); + + // Only initialize tracking if it doesn't exist already + const trackingExists = projectTrackingMap[projectToSelect.projectId]; + if (!trackingExists) { + const initialElapsed = parseTimeStringToSeconds(projectToSelect.totalWorkTime); + dispatch( + initializeProjectTracking({ + projectId: projectToSelect.projectId, + initialTime: initialElapsed, + }) + ); + } else { + // Update project data but preserve tracking state + dispatch( + updateProjectTracking({ + projectId: projectToSelect.projectId, + data: { + ...trackingExists, + // Update elapsed time from server if needed + elapsedTime: parseTimeStringToSeconds(projectToSelect.totalWorkTime), + } + }) + ); + } + } + + fetchProfile(); } else { console.warn("⚠️ Unexpected response:", res); } } catch (err) { console.error("❌ Error fetching projects:", err); } finally { - setIsRefreshing(false) + setIsRefreshing(false); } }; @@ -246,7 +279,7 @@ const Dashboard: React.FC = () => { }; return ( -
+
{/* Header */}

Dashboard

@@ -272,6 +305,11 @@ const Dashboard: React.FC = () => {

{formatTime(displayTime)}

+ + {/*

+ {formatTime(displayTime)} + {isLimitReached && " ⚠️"} +

*/}
Project:{" "} @@ -330,15 +368,16 @@ const Dashboard: React.FC = () => { {/* 🔹 Sticky Header */}

Projects

+ {!selectedData?.isTracking && + }
{/* 🔹 Table or Empty State or Skeleton */} @@ -362,7 +401,7 @@ const Dashboard: React.FC = () => { minHeight: "auto", }} > - +
diff --git a/src/ui/pages/HelpSupport/HelpSupport.tsx b/src/ui/pages/HelpSupport/HelpSupport.tsx index 3d28484..6210087 100644 --- a/src/ui/pages/HelpSupport/HelpSupport.tsx +++ b/src/ui/pages/HelpSupport/HelpSupport.tsx @@ -23,7 +23,7 @@ const HelpSupport: React.FC = () => {

Email

-

support@vatechcorp.com

+

support@docva.com

@@ -31,7 +31,7 @@ const HelpSupport: React.FC = () => {

Phone

-

+91 98765 43210

+

+XXXX XXXXX

diff --git a/src/ui/pages/LeaveAndAdjustments/tabs/AdjustmentTab.tsx b/src/ui/pages/LeaveAndAdjustments/tabs/AdjustmentTab.tsx index 7e99b90..bd9c521 100644 --- a/src/ui/pages/LeaveAndAdjustments/tabs/AdjustmentTab.tsx +++ b/src/ui/pages/LeaveAndAdjustments/tabs/AdjustmentTab.tsx @@ -464,7 +464,7 @@ const AdjustmentTab: React.FC = ({ projects }) => { console.log("Timesheet Created Successfully"); resetForm(); // Show success toast instead of alert - showToast("PTO Request submitted successfully!", "success"); + showToast("Timesheet Request submitted successfully!", "success"); // Navigate after a small delay setTimeout(() => { @@ -680,4 +680,5 @@ const AdjustmentTab: React.FC = ({ projects }) => { ); }; -export default AdjustmentTab; \ No newline at end of file +export default AdjustmentTab; + diff --git a/src/ui/pages/PaySlips/PaysSlips.tsx b/src/ui/pages/PaySlips/PaysSlips.tsx index d3d9ca5..5c9b931 100644 --- a/src/ui/pages/PaySlips/PaysSlips.tsx +++ b/src/ui/pages/PaySlips/PaysSlips.tsx @@ -84,7 +84,7 @@ const PaysSlips = () => {
- + {/* */} @@ -111,13 +111,13 @@ const PaysSlips = () => { - + */} ))} diff --git a/src/ui/pages/Settings/ChangePasswordModal.tsx b/src/ui/pages/Settings/ChangePasswordModal.tsx index 4d6c5bf..52cbd08 100644 --- a/src/ui/pages/Settings/ChangePasswordModal.tsx +++ b/src/ui/pages/Settings/ChangePasswordModal.tsx @@ -1,6 +1,7 @@ // src/ui/pages/Settings/ChangePasswordModal.tsx import React, { useState } from "react"; import { Eye, EyeOff } from "lucide-react"; +import { useToast } from "../../helper/ToastContext"; interface ChangePasswordModalProps { isOpen: boolean; @@ -19,12 +20,13 @@ const ChangePasswordModal: React.FC = ({ const [showOld, setShowOld] = useState(false); const [showNew, setShowNew] = useState(false); const [showConfirm, setShowConfirm] = useState(false); + const { showToast } = useToast(); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (newPassword !== confirmPassword) { - alert("New passwords do not match!"); + showToast("New passwords do not match!" , "error"); return; } @@ -40,17 +42,17 @@ const ChangePasswordModal: React.FC = ({ const response = await window.electron.changePassword(payload); if (response.success) { - alert("Password changed successfully!"); + showToast("Password changed successfully!" , "success"); setOldPassword("") setNewPassword("") setConfirmPassword("") onClose(); } else { - alert(response.message || "Failed to change password."); + showToast((response.message || "Failed to change password.") , "error"); } } catch (error) { console.error("Password change error:", error); - alert("Error changing password."); + showToast("Error changing password." , "error"); } finally { setLoading(false); } diff --git a/src/ui/pages/Timesheets/ProjectLogsModal.tsx b/src/ui/pages/Timesheets/ProjectLogsModal.tsx index 7144932..99855b8 100644 --- a/src/ui/pages/Timesheets/ProjectLogsModal.tsx +++ b/src/ui/pages/Timesheets/ProjectLogsModal.tsx @@ -1,3 +1,4 @@ +import { DateTime } from "luxon"; import React from "react"; interface TabActivity { @@ -27,6 +28,21 @@ interface Props { const ProjectLogsModal: React.FC = ({ isOpen, onClose, projectName, logs, loading }) => { if (!isOpen) return null; + + const getTimeDifference = (from: string, to: string) => { + const start = DateTime.fromISO(from); + const end = DateTime.fromISO(to); + + const diff = end.diff(start, ["hours", "minutes", "seconds"]); + + return { + hours: diff.hours, + minutes: diff.minutes, + seconds: Math.floor(diff.seconds), + formatted: `${diff.hours}h ${diff.minutes}m ${Math.floor(diff.seconds)}s` + }; + }; + return (
@@ -46,7 +62,11 @@ const ProjectLogsModal: React.FC = ({ isOpen, onClose, projectName, logs,
- 🕒 Session {idx + 1} — {session.totalSessionTime} + 🕒 Session {idx + 1} — {getTimeDifference( + new Date(session.from).toISOString(), + new Date(session.to).toISOString() + ).formatted} + {new Date(session.from).toLocaleTimeString()} -{" "} @@ -69,10 +89,10 @@ const ProjectLogsModal: React.FC = ({ isOpen, onClose, projectName, logs,
- + ))} diff --git a/src/ui/pages/Timesheets/Timesheets.tsx b/src/ui/pages/Timesheets/Timesheets.tsx index b6cc269..8ca6afc 100644 --- a/src/ui/pages/Timesheets/Timesheets.tsx +++ b/src/ui/pages/Timesheets/Timesheets.tsx @@ -52,10 +52,11 @@ const Timesheets: React.FC = () => { const { windowLogs } = useSelector((state: RootState) => state.window); + console.log(" Timesheet in component:", windowLogs); const [projectsData, setProjectsData] = useState([]); const [logs, setLogs] = useState([]); - const [selectedProject, setSelectedProject] = useState(null); + // const [selectedProject, setSelectedProject] = useState(null); const [selectedProjectName, setSelectedProjectName] = useState(""); const [modalOpen, setModalOpen] = useState(false); const [loading, setLoading] = useState(false); @@ -63,9 +64,13 @@ const Timesheets: React.FC = () => { const [lastRefreshed, setLastRefreshed] = useState(null); const [initialLoading, setInitialLoading] = useState(true); const [tick, setTick] = useState(0); + const { selectedProject } = + useSelector((state: any) => state.projects); const activeProjects = projectsData.filter(p => p.status === "ACTIVE"); + console.log("selectedProject", selectedProject.projectId) + useEffect(() => { const interval = setInterval(() => { @@ -84,7 +89,6 @@ const Timesheets: React.FC = () => { }; - const fetchProjects = async () => { setInitialLoading(true); try { @@ -120,7 +124,7 @@ const Timesheets: React.FC = () => { try { setModalOpen(true); setLoading(true); - setSelectedProject(projectID); + // setSelectedProject(projectID); setSelectedProjectName(projectName); console.log("projectID", projectID) @@ -154,10 +158,6 @@ const Timesheets: React.FC = () => { return parts.slice(0, 2).join(" "); }; - - - - // Skeleton loader rows const renderSkeletonRows = () => { return Array.from({ length: 5 }).map((_, index) => ( @@ -225,13 +225,13 @@ const Timesheets: React.FC = () => {
#Date Total Hours Total PayActionAction
${row.totalPayAmount.toFixed(2)} + {/* -
{tabIdx + 1} - {tab.title}
- {tab.appName} + {tab.title}
+ {tab.appName}
{new Date(tab.from).toLocaleTimeString()}
{new Date(tab.to).toLocaleTimeString()}
{new Date(tab.from).toLocaleTimeString()}
{new Date(tab.to).toLocaleTimeString()}
{tab.durationFormatted}
{formatWorkTime(entry.totalWorkTime)} - - - ); - } + setVpnConnected(res?.success === true); + } catch (error) { + console.error("VPN check failed:", error); + setVpnConnected(false); + } finally { + setChecking(false); + setIsRetrying(false); + } + }; + + useEffect(() => { + checkVPN(); + }, []); + + const connectVPN = async () => { + setChecking(true); + try { + const res = await window.electron.connectVPN(); + console.log("connectVPN result → ", res); + + // Only proceed if VPN actually connected + if (res === "Windows VPN connected silently") { + + const vpnRes = await window.electron.updateBaseURL(VPN_API); + console.log("vpnRes", vpnRes); + + if (vpnRes) { + // Run VPN check again AFTER URL changes + await checkVPN(); + } else { + console.warn("Base URL updated but health check failed"); + } + } + } catch (error) { + console.log(error); + } finally { + setChecking(false); + } + }; + + + // Checking state + if (checking && !isRetrying) { + return ( +
+
+
+
+
+
+ +
+
+
+

+ Verifying Connection +

+

Checking VPN status...

+
+ +
+
+
+ ); + } + + // VPN not connected state + if (!vpnConnected) { + return ( +
+
+
+
+ {/* Icon with animation */} +
+
+
+
+
+ +
+
+ + {/* Title */} +

+ VPN Required +

+ + {/* Description */} +

+ Please connect to your company VPN to access this application. +

+ + {/* Retry button */} + + + {/* Help text */} +
+

+ Need help? Contact IT support at{" "} + + support@company.com + +

+
+
+
+
+ ); + } + + return <>{children}; - return <>{children}; }; -export default VpnGuard; +export default VpnGuard; \ No newline at end of file diff --git a/types.d.ts b/types.d.ts index 273cad3..d080cd6 100644 --- a/types.d.ts +++ b/types.d.ts @@ -133,6 +133,15 @@ interface PayslipResponse { } +interface RegionResponse { + success: boolean; + status: number; + data?: any; + retry?: boolean; + message?: string; +} + + interface Window { electron: { login: (credentials: { email: string; password: string }) => Promise; @@ -242,8 +251,14 @@ interface Window { data?: any; }>; + checkRegion: () => Promise; connectVPN: () => Promise; disconnectVPN: () => Promise; + getBaseURL: () => Promise; + updateBaseURL: (url: string) => Promise<{ + updated: boolean; + baseURL: string; + }>; }; }