From 1cc863d3c9502a10fea16f93c4a0b361206e0f51 Mon Sep 17 00:00:00 2001 From: JaiPandey425 Date: Mon, 8 Dec 2025 16:50:08 +0530 Subject: [PATCH 01/14] isLimit end then change the color working but comment --- .env | 0 src/electron/api/axiosClient.ts | 2 +- src/electron/api/regionCheck.ts | 57 +++++++++++++ src/electron/ipc/ipcHandler.ts | 8 ++ src/electron/preload.cts | 2 + src/ui/App.tsx | 19 +++-- src/ui/layout/DashboardLayout.tsx | 2 + src/ui/pages/Dashboard.tsx | 8 +- .../tabs/AdjustmentTab.tsx | 5 +- src/ui/pages/Vpn/VpnGuard.tsx | 82 ++++++++++--------- types.d.ts | 11 +++ 11 files changed, 146 insertions(+), 50 deletions(-) create mode 100644 .env create mode 100644 src/electron/api/regionCheck.ts diff --git a/.env b/.env new file mode 100644 index 0000000..e69de29 diff --git a/src/electron/api/axiosClient.ts b/src/electron/api/axiosClient.ts index 53b5896..cc0acd5 100644 --- a/src/electron/api/axiosClient.ts +++ b/src/electron/api/axiosClient.ts @@ -71,4 +71,4 @@ api.interceptors.response.use( } ); -export default api; +export default api; \ No newline at end of file diff --git a/src/electron/api/regionCheck.ts b/src/electron/api/regionCheck.ts new file mode 100644 index 0000000..e4d1c45 --- /dev/null +++ b/src/electron/api/regionCheck.ts @@ -0,0 +1,57 @@ + +import axios from "axios"; +import https from "https"; + +const httpsAgent = new https.Agent({ rejectUnauthorized: false }); + +export const checkRegion = async () => { + const baseURL = "https://daniela-nonprofessed-boastfully.ngrok-free.app"; + + try { + + const res = await axios.get(`${baseURL}/health`, { + headers: { "ngrok-skip-browser-warning": "true" }, + httpsAgent + }); + + return { + success: true, + status: res.status, + data: res.data + }; + + } catch (error: any) { + const status = error?.response?.status || 500; + + console.log("Region Check Error:", status); + + // πŸ” Auto retry if 403 β€” server blocked first try + if (status === 403) { + try { + const retry = await axios.get(`${baseURL}/api`, { + headers: { "ngrok-skip-browser-warning": "true" }, + httpsAgent + }); + + return { + success: true, + status: retry.status, + data: retry.data, + retry: true + }; + } catch (retryErr: any) { + return { + success: false, + status: retryErr?.response?.status || 500, + message: "403 Retry failed" + }; + } + } + + return { + success: false, + status, + message: "Region check failed" + }; + } +}; diff --git a/src/electron/ipc/ipcHandler.ts b/src/electron/ipc/ipcHandler.ts index edaa1df..8465665 100644 --- a/src/electron/ipc/ipcHandler.ts +++ b/src/electron/ipc/ipcHandler.ts @@ -3,6 +3,7 @@ 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"; @@ -10,6 +11,8 @@ import { getMainWindow } from '../main.js'; export const registerIPCHandlers = (mainWindow: BrowserWindow) => { console.log("βš™οΈ Registering IPC Handlers..."); + + ipcMain.handle("get-token", async (_, key: "ACCESS_TOKEN" | "REFRESH_TOKEN") => { return await getToken(key); }); @@ -534,6 +537,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 870cf71..171a4ef 100644 --- a/src/electron/preload.cts +++ b/src/electron/preload.cts @@ -65,6 +65,8 @@ contextBridge.exposeInMainWorld('electron', { getLeaveList: () => ipcRenderer.invoke("get-leave-list"), getAdjustmentsList:(vaId: string) => ipcRenderer.invoke("get-adjustments-list",vaId), + checkRegion: () => ipcRenderer.invoke("check-region") + } satisfies Window['electron']); // function ipcInvoke( diff --git a/src/ui/App.tsx b/src/ui/App.tsx index 7de1c12..b55a71b 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 46d39d8..c24749b 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]); diff --git a/src/ui/pages/Dashboard.tsx b/src/ui/pages/Dashboard.tsx index 8130cbe..ebf8f52 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) @@ -156,7 +157,7 @@ const Dashboard: React.FC = () => { const firstProject = res.data[0]; - console.log("Line 158" , firstProject) + console.log("Line 158", firstProject) // Select the project dispatch(setSelectedProjectSlice(firstProject)); @@ -272,6 +273,11 @@ const Dashboard: React.FC = () => {

{formatTime(displayTime)}

+ + {/*

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

*/}
Project:{" "} 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/Vpn/VpnGuard.tsx b/src/ui/pages/Vpn/VpnGuard.tsx index 5032cb9..f4bea63 100644 --- a/src/ui/pages/Vpn/VpnGuard.tsx +++ b/src/ui/pages/Vpn/VpnGuard.tsx @@ -1,54 +1,58 @@ import React, { useEffect, useState } from "react"; interface Props { - children: React.ReactNode; + children: React.ReactNode; } const VpnGuard: React.FC = ({ children }) => { - const [checking, setChecking] = useState(true); - const [vpnConnected, setVpnConnected] = useState(true); + const [checking, setChecking] = useState(true); + const [vpnConnected, setVpnConnected] = useState(true); -// const checkVPN = async () => { -// try { -// const res = await window.electron.checkVpnConnection(); -// setVpnConnected(res?.success); -// } catch { -// setVpnConnected(false); -// } finally { -// setChecking(false); -// } -// }; + const checkVPN = async () => { + setChecking(true) + try { + const res = await window.electron.checkRegion(); + console.log("VpnGuard", res) + if (res.success) { + setVpnConnected(true); + } + } catch { + setVpnConnected(false); + } finally { + setChecking(false); + } + }; - useEffect(() => { - // checkVPN(); - }, []); + useEffect(() => { + checkVPN(); + }, []); - if (checking) { - return ( -
- - Checking VPN... -
- ); - } + if (checking) { + return ( +
+ + Checking VPN... +
+ ); + } - if (!vpnConnected) { - return ( -
-
-

VPN Required

-

Please connect to your company VPN to continue.

+ if (!vpnConnected) { + return ( +
+
+

VPN Required

+

Please connect to your company VPN to continue.

- -
-
- ); - } + +
+
+ ); + } - return <>{children}; + return <>{children}; }; export default VpnGuard; diff --git a/types.d.ts b/types.d.ts index 8f20363..70f38ce 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; @@ -241,6 +250,8 @@ interface Window { success: boolean; data?: any; }>; + + checkRegion: () => Promise; }; } From 6c223f01c9690ad0a66c21e57fe5aa8dcf3b578f Mon Sep 17 00:00:00 2001 From: JaiPandey425 Date: Wed, 10 Dec 2025 16:08:04 +0530 Subject: [PATCH 02/14] timesheet session time update --- resources/wireguard/windows/wg0.conf | 4 +- src/electron/api/axiosClient.ts | 7 +- src/electron/api/baseURL.ts | 2 +- src/electron/utils/utilityFunctions.ts | 2 +- src/ui/App.tsx | 4 +- src/ui/pages/Dashboard.tsx | 81 +++++++++++++------ src/ui/pages/HelpSupport/HelpSupport.tsx | 4 +- src/ui/pages/PaySlips/PaysSlips.tsx | 6 +- src/ui/pages/Settings/ChangePasswordModal.tsx | 10 ++- src/ui/pages/Timesheets/ProjectLogsModal.tsx | 28 ++++++- 10 files changed, 101 insertions(+), 47 deletions(-) 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 ec2d7eb..1ba36c5 100644 --- a/src/electron/api/axiosClient.ts +++ b/src/electron/api/axiosClient.ts @@ -4,7 +4,6 @@ import { getToken } from '../utils/tokenHelper.js'; import chalk from "chalk"; import { sendTokenExpired } from '../ipc/ipcHandler.js'; import https from 'https'; -import { getBaseURL } from './baseURL.js'; const TOKEN_KEY = 'ACCESS_TOKEN'; @@ -13,7 +12,7 @@ const httpsAgent = new https.Agent({ }); const api: AxiosInstance = axios.create({ - baseURL: await getBaseURL(), // πŸ” your backend + baseURL: 'https://quality-friendly-goldfish.ngrok-free.app/api/v1/va', // πŸ” your backend httpsAgent // timeout: 10000, }); @@ -30,7 +29,7 @@ api.interceptors.request.use( if (token) { config.headers['token'] = token; - config.headers['ngrok-skip-browser-warning'] = 'true'; + config.headers['ngrok-skip-browser-warning'] = 'true'; } return config; @@ -55,7 +54,7 @@ api.interceptors.response.use( (response: AxiosResponse) => response.data, async (error) => { - console.log(error, "Line 46") + console.log(error , "Line 46") const status = error?.response?.status; console.error('[Axios Error]', status); diff --git a/src/electron/api/baseURL.ts b/src/electron/api/baseURL.ts index 1982a4a..f380736 100644 --- a/src/electron/api/baseURL.ts +++ b/src/electron/api/baseURL.ts @@ -1,4 +1,4 @@ -let CURRENT_BASE_URL = "https://portal-sf.docva.com"; +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) => { diff --git a/src/electron/utils/utilityFunctions.ts b/src/electron/utils/utilityFunctions.ts index d8523d5..87e8175 100644 --- a/src/electron/utils/utilityFunctions.ts +++ b/src/electron/utils/utilityFunctions.ts @@ -71,7 +71,7 @@ export const handleLogin = async (credentials: { email: string, password: string try { console.log("➑️ Calling API..."); const response = await axios.post( - `${baseURL}/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 b55a71b..9852676 100644 --- a/src/ui/App.tsx +++ b/src/ui/App.tsx @@ -123,9 +123,9 @@ const App: React.FC = () => { path="/login" element={ - + {/* */} - + {/* */} } /> diff --git a/src/ui/pages/Dashboard.tsx b/src/ui/pages/Dashboard.tsx index ebf8f52..46c89f5 100644 --- a/src/ui/pages/Dashboard.tsx +++ b/src/ui/pages/Dashboard.tsx @@ -130,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( @@ -152,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); } }; @@ -247,7 +279,7 @@ const Dashboard: React.FC = () => { }; return ( -
+
{/* Header */}

Dashboard

@@ -336,15 +368,16 @@ const Dashboard: React.FC = () => { {/* πŸ”Ή Sticky Header */}

Projects

+ {!selectedData?.isTracking && + }
{/* πŸ”Ή Table or Empty State or Skeleton */} @@ -368,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/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,
- + ))} From 882f4acad859b4adf276e762052eda86a6b12628 Mon Sep 17 00:00:00 2001 From: JaiPandey425 Date: Wed, 10 Dec 2025 18:46:57 +0530 Subject: [PATCH 03/14] timesheet --- src/ui/pages/Timesheets/Timesheets.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) 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)} + */} {/* Header */} {isTracking && From 94840a93dd36667b19cfde6cc2f796489f6ec085 Mon Sep 17 00:00:00 2001 From: JaiPandey425 Date: Thu, 11 Dec 2025 14:05:33 +0530 Subject: [PATCH 05/14] add vpn v2 --- resources/wireguard/windows/wg0.conf | 2 +- src/electron/api/axiosClient.ts | 13 ++++-- src/electron/api/baseURL.ts | 3 +- src/electron/utils/utilityFunctions.ts | 2 +- src/ui/App.tsx | 4 +- src/ui/pages/Vpn/VpnGuard.tsx | 64 ++++++++++++++++++++------ 6 files changed, 65 insertions(+), 23 deletions(-) diff --git a/resources/wireguard/windows/wg0.conf b/resources/wireguard/windows/wg0.conf index 69a3e03..fa2a861 100644 --- a/resources/wireguard/windows/wg0.conf +++ b/resources/wireguard/windows/wg0.conf @@ -6,5 +6,5 @@ DNS = 1.1.1.1 [Peer] PublicKey = jG+7H5thEiNjxY1XqI1R9oyMAMTIegLwE3tZkS1Rj2U= Endpoint = 35.95.209.161:51820 -AllowedIPs = 10.0.0.0/32 0.0.0.0/0 +AllowedIPs = 10.0.0.0/32 PersistentKeepalive = 25 diff --git a/src/electron/api/axiosClient.ts b/src/electron/api/axiosClient.ts index 1ba36c5..bb23df0 100644 --- a/src/electron/api/axiosClient.ts +++ b/src/electron/api/axiosClient.ts @@ -4,15 +4,22 @@ import { getToken } from '../utils/tokenHelper.js'; import chalk from "chalk"; import { sendTokenExpired } from '../ipc/ipcHandler.js'; import https from 'https'; +import { getBaseURL } from './baseURL.js'; const TOKEN_KEY = 'ACCESS_TOKEN'; +const Base_URL = await getBaseURL() + const httpsAgent = new https.Agent({ rejectUnauthorized: false }); + +console.log('Base_URL$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$' ,Base_URL) + const api: AxiosInstance = axios.create({ - baseURL: 'https://quality-friendly-goldfish.ngrok-free.app/api/v1/va', // πŸ” your backend + // baseURL: 'https://quality-friendly-goldfish.ngrok-free.app/api/v1/va', // πŸ” your backend + baseURL: `${Base_URL}/api/v1/va`, httpsAgent // timeout: 10000, }); @@ -29,7 +36,7 @@ api.interceptors.request.use( if (token) { config.headers['token'] = token; - config.headers['ngrok-skip-browser-warning'] = 'true'; + config.headers['ngrok-skip-browser-warning'] = 'true'; } return config; @@ -54,7 +61,7 @@ api.interceptors.response.use( (response: AxiosResponse) => response.data, async (error) => { - console.log(error , "Line 46") + console.log(error, "Line 46") const status = error?.response?.status; console.error('[Axios Error]', status); diff --git a/src/electron/api/baseURL.ts b/src/electron/api/baseURL.ts index f380736..67478cd 100644 --- a/src/electron/api/baseURL.ts +++ b/src/electron/api/baseURL.ts @@ -1,4 +1,5 @@ -let CURRENT_BASE_URL = "https://quality-friendly-goldfish.ngrok-free.app/api/v1/va"; +// let CURRENT_BASE_URL = "https://quality-friendly-goldfish.ngrok-free.app/api/v1/va"; +let CURRENT_BASE_URL = "https://quality-friendly-goldfish.ngrok-free.app"; export const getBaseURL = () => CURRENT_BASE_URL; export const setBaseURL = (url: string) => { diff --git a/src/electron/utils/utilityFunctions.ts b/src/electron/utils/utilityFunctions.ts index 87e8175..d8523d5 100644 --- a/src/electron/utils/utilityFunctions.ts +++ b/src/electron/utils/utilityFunctions.ts @@ -71,7 +71,7 @@ export const handleLogin = async (credentials: { email: string, password: string try { console.log("➑️ Calling API..."); const response = await axios.post( - `https://quality-friendly-goldfish.ngrok-free.app/api/v1/va/login`, + `${baseURL}/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 9852676..b55a71b 100644 --- a/src/ui/App.tsx +++ b/src/ui/App.tsx @@ -123,9 +123,9 @@ const App: React.FC = () => { path="/login" element={ - {/* */} + - {/* */} + } /> diff --git a/src/ui/pages/Vpn/VpnGuard.tsx b/src/ui/pages/Vpn/VpnGuard.tsx index 9bf8c2f..609dc36 100644 --- a/src/ui/pages/Vpn/VpnGuard.tsx +++ b/src/ui/pages/Vpn/VpnGuard.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from "react"; -import { Shield, ShieldAlert, RefreshCw, Wifi } from "lucide-react"; +import { Shield, ShieldAlert, RefreshCw, Wifi, Power } from "lucide-react"; interface Props { children: React.ReactNode; @@ -8,6 +8,7 @@ interface Props { const VpnGuard: React.FC = ({ children }) => { const VPN_API = "http://10.0.0.2:3000"; + const DEFAULT_API = "https://quality-friendly-goldfish.ngrok-free.app" const [checking, setChecking] = useState(true); const [vpnConnected, setVpnConnected] = useState(true); @@ -19,8 +20,10 @@ const VpnGuard: React.FC = ({ children }) => { const res = await window.electron?.checkRegion(); console.log("VpnGuard", res); + setVpnConnected(false); + return - setVpnConnected(res?.success === true); + // setVpnConnected(res?.success === true); } catch (error) { console.error("VPN check failed:", error); setVpnConnected(false); @@ -61,6 +64,27 @@ const VpnGuard: React.FC = ({ children }) => { }; + const disconnectVPN = async () => { + setChecking(true); + try { + const res = await window.electron.disconnectVPN();; + console.log("disconnectVPN β†’", res); + + // Restore original base URL + const baseRes = await window.electron.updateBaseURL(DEFAULT_API); + console.log("baseRes disconnect β†’", baseRes); + + setVpnConnected(true); + + // await checkVPN(); + } catch (error) { + console.log("Disconnect Error:", error); + } finally { + setChecking(false); + } + }; + + // Checking state if (checking && !isRetrying) { return ( @@ -93,7 +117,8 @@ const VpnGuard: React.FC = ({ children }) => {
- {/* Icon with animation */} + + {/* Icon */}
@@ -103,21 +128,16 @@ const VpnGuard: React.FC = ({ children }) => {
- {/* Title */} -

- VPN Required -

- - {/* Description */} +

VPN Required

Please connect to your company VPN to access this application.

- {/* Retry button */} + {/* Connect VPN */} - {/* Help text */} + {/* Disconnect VPN (Disabled when NOT connected) */} + +

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

@@ -147,7 +176,12 @@ const VpnGuard: React.FC = ({ children }) => { ); } - return <>{children}; + + return ( + <> + {children} + + ); }; From 68eb8539876abf935c2fad0ee2218748f6c6073e Mon Sep 17 00:00:00 2001 From: JaiPandey425 Date: Thu, 11 Dec 2025 19:44:58 +0530 Subject: [PATCH 06/14] change the tabs logs data login when stop send logs tabs data then logout --- src/electron/ipc/ipcHandler.ts | 61 ++-- src/ui/hooks/useActiveAppTracker.tsx | 424 +++++++++++++-------------- src/ui/layout/DashboardLayout.tsx | 281 ++++++++---------- src/ui/store/index.tsx | 2 + src/ui/store/logsSlice.tsx | 182 ++++++++++++ types.d.ts | 27 +- 6 files changed, 552 insertions(+), 425 deletions(-) create mode 100644 src/ui/store/logsSlice.tsx diff --git a/src/electron/ipc/ipcHandler.ts b/src/electron/ipc/ipcHandler.ts index d88f698..06eb15a 100644 --- a/src/electron/ipc/ipcHandler.ts +++ b/src/electron/ipc/ipcHandler.ts @@ -15,17 +15,17 @@ export const registerIPCHandlers = (mainWindow: BrowserWindow) => { ipcMain.handle("GET_BASE_URL", () => { - return getBaseURL(); -}); + return getBaseURL(); + }); -// UPDATE Base URL -ipcMain.handle("UPDATE_BASE_URL", (event, url: string) => { - setBaseURL(url); - return { - updated: true, - baseURL: url - }; -}); + // 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); @@ -168,39 +168,30 @@ ipcMain.handle("stop-project-tracking", async (event, payload) => { // tabs opening and closing when and tracking start ipcMain.handle("tracking-tabs-opening-and-closing", async (event, payload) => { try { - console.log("🟦 IPC tracking-tabs-opening-and-closing:", payload); + console.log("🟦 IPC received payload:", payload); - let response; - if (!payload.id) { - // POST new log (new tab opened) - response = await api.post("/log/project/track", { - logId: payload.logId, - projectId: payload.projectId, - title: payload.title, - appName: payload.appName, - }); - console.log(" New log created:", response); - } else { - // PATCH existing log (same tab, update) - response = await api.patch("/log/project/track", { - id: payload.id, - logId: payload.logId, - projectId: payload.projectId, - title: payload.title, - appName: payload.appName, - }); - console.log("Log updated:", response); - } + const response = await api.post("/log/project/track", payload); - return response; + console.log("🟩 Log track API success:", response.data); + + return { + success: true, + data: response.data, + }; } catch (error) { - console.error("Error syncing project tab tracking:", error); - throw error; + console.error("❌ Log track API error:", error); + + return { + success: false, + message: "Something went wrong", + error, + }; } }); + ipcMain.handle("get-project-logs", async (event, { projectID, fromDate, toDate }) => { try { console.log("πŸ“₯ get-project-logs called with:", { projectID, fromDate, toDate }); diff --git a/src/ui/hooks/useActiveAppTracker.tsx b/src/ui/hooks/useActiveAppTracker.tsx index 3099d19..8d24af1 100644 --- a/src/ui/hooks/useActiveAppTracker.tsx +++ b/src/ui/hooks/useActiveAppTracker.tsx @@ -1,6 +1,14 @@ +// hooks/useActiveAppTracker.tsx import { useEffect, useRef, useCallback } from "react"; -import { useDispatch } from "react-redux"; -import { setActiveWindow, WindowInfo } from "../store/windowSlice"; +import { useDispatch, useSelector } from "react-redux"; +import { RootState } from "../store"; +import { WindowInfo } from "../store/windowSlice"; +import { + initializeProjectLogs, + updateWindowLog, + clearProjectLogs, + debugLogsState, +} from "../store/logsSlice"; interface DailyHours { todayTotal: string; @@ -16,273 +24,253 @@ export const useActiveAppTracker = ( onLimitExceeded?: () => void ) => { const dispatch = useDispatch(); + const { logsByProject } = useSelector((state: RootState) => state.logs); const lastActiveRef = useRef(null); - const lastLogIdRef = useRef(null); const lastTitleRef = useRef(null); - const intervalIdRef = useRef(null); - const finalizeTrackingRef = useRef<(() => Promise) | null>(null); - const limitCheckedRef = useRef(false); - const isStoppingRef = useRef(false); // βœ… Flag to prevent new requests + const lastAppNameRef = useRef(null); + const intervalIdRef = useRef | null>(null); + const limitCheckedRef = useRef(false); + const isStoppingRef = useRef(false); + const checkCountRef = useRef(0); - const canSend = !!(activeProjectLogId && projectId && !isStoppingRef.current); + // Initialize logs when tracking starts + useEffect(() => { + if (isTracking && activeProjectLogId && projectId) { + // Initialize fresh logs for this tracking session + dispatch( + initializeProjectLogs({ + projectId, + logId: activeProjectLogId, + }) + ); + console.log(` Logs initialized for project: ${projectName} (${projectId})`); - // βœ… Parse time string to minutes - const parseTimeToMinutes = (timeStr: string): number => { - const hourMatch = timeStr.match(/(\d+)h/); - const minMatch = timeStr.match(/(\d+)m/); - - const hours = hourMatch ? parseInt(hourMatch[1]) : 0; - const minutes = minMatch ? parseInt(minMatch[1]) : 0; - - return hours * 60 + minutes; - }; - - // βœ… Check if daily limit is exceeded - const checkDailyLimit = (dailyHours?: DailyHours): boolean => { - if (!dailyHours) return false; - - const todayTotalMins = parseTimeToMinutes(dailyHours.todayTotal); - const dailyLimitMins = parseTimeToMinutes(dailyHours.dailyLimit); - const remainingMins = parseTimeToMinutes(dailyHours.remaining); - - console.log("⏰ Daily Limit Check:", { - todayTotal: `${todayTotalMins} mins`, - dailyLimit: `${dailyLimitMins} mins`, - remaining: `${remainingMins} mins`, - exceeded: remainingMins <= 0 - }); - - if (remainingMins <= 0) { - return true; + // Reset refs + lastActiveRef.current = null; + lastTitleRef.current = null; + lastAppNameRef.current = null; + checkCountRef.current = 0; + isStoppingRef.current = false; } + }, [isTracking, activeProjectLogId, projectId, projectName, dispatch]); - return false; - }; - - // βœ… Send activity log (POST for new, PATCH for update) - const sendActivityLog = async ( - projectLogId: string, - projectId: string, - title: string, - appName: string, - isNew: boolean - ) => { - // βœ… CRITICAL: Block all requests when stopping - if (isStoppingRef.current) { - console.log("🚫 Stopping in progress β€” blocking activity log"); - return null; - } - - if (!canSend) { - console.log("🚫 Skipping API β€” missing required IDs"); - return null; - } - - try { - const payload: { - id?: string; - logId: string; - projectId: string; - title: string; - appName: string; - } = { - logId: projectLogId, - projectId, - title, - appName, - }; - - if (!isNew && lastLogIdRef.current) { - payload.id = lastLogIdRef.current; - } - - console.log(isNew ? "➑️ POST new log" : "✏️ PATCH update log", payload); - - const res = await window.electron.trackingTabsOpeningClosing(payload); - - console.log("πŸ“Š Activity log response:", res); - - // Store the log ID for future updates - if (isNew && res?.data?._id) { - lastLogIdRef.current = res.data._id; - console.log("πŸ’Ύ Stored log ID:", res.data._id); - } - - // βœ… Check daily limit after every API call - if (res?.dailyHours) { - const limitExceeded = checkDailyLimit(res.dailyHours); - - if (limitExceeded && !limitCheckedRef.current) { - limitCheckedRef.current = true; - console.warn("🚨 Daily limit exceeded!"); - - if (onLimitExceeded) { - setTimeout(() => { - onLimitExceeded(); - }, 500); - } - } - } - - return res; - } catch (error) { - console.error("❌ Activity log error:", error); - throw error; - } - }; - - // βœ… Stop the interval immediately - const stopInterval = useCallback(() => { - console.log("πŸ›‘ Stopping activity tracking interval..."); + // Stop tracking intervals + const stopIntervals = useCallback(() => { if (intervalIdRef.current) { clearInterval(intervalIdRef.current); intervalIdRef.current = null; - console.log("βœ… Interval cleared"); } }, []); - // βœ… Send final PATCH before stopping (called by handleStopTracking) - const sendFinalPatch = useCallback(async () => { - console.log("=".repeat(60)); - console.log("πŸ›‘ FINAL PATCH INITIATED"); - console.log("=".repeat(60)); - - // βœ… STEP 1: Set stopping flag IMMEDIATELY - isStoppingRef.current = true; - console.log("πŸ”’ Stopping flag set β€” no new requests allowed"); - - // βœ… STEP 2: Stop the interval IMMEDIATELY - stopInterval(); - - if (!activeProjectLogId || !projectId) { - console.log("⚠️ Skipping final PATCH β€” no active project log ID"); - return; + // Debug function to check logs state + const debugCurrentLogs = useCallback(() => { + if (projectId) { + dispatch(debugLogsState({ projectId })); } + }, [projectId, dispatch]); + + // Finalize and send all logs when stopping tracking + const finalizeAndSendLogs = useCallback( + async (vaId: string): Promise => { + // console.log("=".repeat(60)); + console.log(" FINALIZING AND SENDING ALL LOGS"); + // console.log("=".repeat(60)); + + // Set stopping flag + isStoppingRef.current = true; + + // Stop intervals immediately + stopIntervals(); + + if (!activeProjectLogId || !projectId) { + console.log("⚠️ Skipping β€” no active project"); + return false; + } + + const projectLogs = logsByProject[projectId]; + if (!projectLogs || projectLogs.logs.length === 0) { + console.log("ℹ️ No logs found for project", projectId); + return true; + } - // βœ… STEP 3: Send ONE final PATCH for the last active window - if (lastLogIdRef.current && lastTitleRef.current && lastActiveRef.current) { try { - console.log("πŸ“Š Final Activity Details:"); - console.log(` β€’ Window: ${lastTitleRef.current}`); - console.log(` β€’ App: ${lastActiveRef.current.owner.name}`); - console.log(` β€’ Log ID: ${lastLogIdRef.current}`); - console.log(` β€’ Project: ${projectId}`); + const nowUTC = new Date().toISOString(); - const result = await sendActivityLog( - activeProjectLogId, - projectId, - lastTitleRef.current, - lastActiveRef.current.owner.name || "Unknown", - false // PATCH (update existing log) - ); + // Finalize the last log entry + const allLogs = [...projectLogs.logs]; + // if (allLogs.length > 0) { + // const lastLog = allLogs[allLogs.length - 1]; + // const endTime = new Date(nowUTC); + // const startTime = new Date(lastLog.from); + // lastLog.to = nowUTC; + // lastLog.totalTime = endTime.getTime() - startTime.getTime(); + // console.log(`πŸ›‘ Finalized last window: ${lastLog.title} (${lastLog.totalTime}ms)`); + // } - console.log("βœ… Final PATCH sent successfully!"); + // Filter out logs with very short duration (less than 1 second) + const validLogs = allLogs.filter((log) => { + const duration = new Date(log.to).getTime() - new Date(log.from).getTime(); + return duration >= 1000; // Minimum 1 second + }); - if (result?.dailyHours) { - console.log("πŸ“ˆ Final Daily Hours:"); - console.log(` β€’ Today: ${result.dailyHours.todayTotal}`); - console.log(` β€’ Limit: ${result.dailyHours.dailyLimit}`); - console.log(` β€’ Remaining: ${result.dailyHours.remaining}`); + console.log(`πŸ“Š Total logs: ${allLogs.length}, Valid logs (β‰₯1s): ${validLogs.length}`); + + if (validLogs.length === 0) { + console.log("ℹ️ No valid logs to send"); + dispatch(clearProjectLogs({ projectId })); + return true; } + // Prepare payload with all valid logs + const payload = { + vaId: vaId || "", + projectId, + logId: activeProjectLogId, + logs: allLogs.map((log) => ({ + title: log.title, + appName: log.appName, + url: log.url ?? "", + from: log.from, + to: log.to, + totalTime: log.totalTime, + })), + }; + + console.log("πŸ“€ Payload to send:", payload); + console.log("πŸ“Š Sending logs summary:", { + projectId, + projectName, + logCount: validLogs.length, + totalTime: validLogs.reduce((sum, log) => sum + log.totalTime, 0), + firstLog: validLogs[0] + ? { + title: validLogs[0].title.substring(0, 40), + appName: validLogs[0].appName, + duration: validLogs[0].totalTime, + from: new Date(validLogs[0].from).toLocaleTimeString(), + to: new Date(validLogs[0].to).toLocaleTimeString(), + } + : null, + lastLog: validLogs[validLogs.length - 1] + ? { + title: validLogs[validLogs.length - 1].title.substring(0, 40), + appName: validLogs[validLogs.length - 1].appName, + duration: validLogs[validLogs.length - 1].totalTime, + from: new Date(validLogs[validLogs.length - 1].from).toLocaleTimeString(), + to: new Date(validLogs[validLogs.length - 1].to).toLocaleTimeString(), + } + : null, + }); + + let res = window.electron.trackingTabsOpeningClosing(payload) + + console.log("Tracking response:", res); + dispatch(clearProjectLogs({ projectId })); + + // Cleanup refs + lastActiveRef.current = null; + lastTitleRef.current = null; + lastAppNameRef.current = null; + limitCheckedRef.current = false; + + console.log("βœ… Logs sent and cleared successfully"); + console.log("=".repeat(60)); + + return true; } catch (error) { - console.error("❌ Final PATCH failed:", error); - throw error; + const errorMsg = error instanceof Error ? error.message : "Unknown error"; + console.error("❌ Error sending logs:", error); + console.log("=".repeat(60)); + return false; } - } else { - console.log("ℹ️ No active window to finalize"); - } + }, + [ + activeProjectLogId, + projectId, + logsByProject, + stopIntervals, + dispatch, + ] + ); - // βœ… STEP 4: Clean up all refs - console.log("🧹 Cleaning up tracking references..."); - lastActiveRef.current = null; - lastTitleRef.current = null; - lastLogIdRef.current = null; - limitCheckedRef.current = false; - console.log("βœ… Cleanup complete"); - console.log("=".repeat(60)); - }, [activeProjectLogId, projectId, stopInterval]); - - // βœ… Update ref whenever sendFinalPatch changes + // Main tracking logic - runs every 5 seconds useEffect(() => { - finalizeTrackingRef.current = sendFinalPatch; - }, [sendFinalPatch]); - - // βœ… Main tracking interval effect - useEffect(() => { - // Reset flags when tracking starts - if (isTracking) { - limitCheckedRef.current = false; - isStoppingRef.current = false; - console.log("🟒 Tracking started β€” flags reset"); - } - - if (!isTracking || !canSend) { - console.log("🟑 Tracking inactive β€” cleaning up"); - stopInterval(); + if (!isTracking || !projectId || !activeProjectLogId) { + console.log("⏸️ Tracking inactive β€” stopping interval"); + stopIntervals(); return; } - console.log(`🟒 Starting active tracker for: ${projectName}`); + console.log(`πŸš€ Starting tracker for: ${projectName}`); - const fetchActiveApp = async () => { - // βœ… Double-check stopping flag before each fetch + const checkActiveWindow = async () => { if (isStoppingRef.current) { - console.log("🚫 Stopping in progress β€” skipping fetch"); return; } try { const details = (await window.electron.getActiveAppDetails()) as WindowInfo; - if (!details || !details.id) return; - const title = details.title; - const appName = details.owner.name; - const prevTitle = lastTitleRef.current; - const hasChanged = !prevTitle || prevTitle !== title; + if (!details || !details.id || !details.owner?.name) { + return; + } - if (hasChanged) { - // PATCH previous window before creating new log - if (lastLogIdRef.current && prevTitle) { - console.log("πŸ”„ Updating previous window:", prevTitle); - await sendActivityLog(activeProjectLogId!, projectId!, prevTitle, appName, false); - } + const currentTitle = details.title; + const currentAppName = details.owner.name; - // POST new window log - console.log("πŸ†• New window detected:", title); - await sendActivityLog(activeProjectLogId!, projectId!, title, appName, true); + checkCountRef.current += 1; - // Update refs and Redux - lastActiveRef.current = details; - lastTitleRef.current = title; - dispatch(setActiveWindow({ window: details, projectId, projectName })); - } else { - console.log("⏸️ Same window, skipping"); + // Update Redux with current window activity + dispatch( + updateWindowLog({ + projectId, + title: currentTitle, + appName: currentAppName, + url: null, + }) + ); + + // Update refs + lastActiveRef.current = details; + lastTitleRef.current = currentTitle; + lastAppNameRef.current = currentAppName; + + // Debug every 10 checks (every 50 seconds) + if (checkCountRef.current % 10 === 0) { + debugCurrentLogs(); } } catch (error) { - console.error("❌ Failed to fetch active app:", error); + console.error("❌ Failed to check active window:", error); } }; - // Initial fetch - fetchActiveApp(); + // Initial check + checkActiveWindow(); // Set up interval (every 5 seconds) - intervalIdRef.current = setInterval(fetchActiveApp, 5000); + intervalIdRef.current = setInterval(checkActiveWindow, 5000); - // Cleanup on unmount + // Cleanup return () => { - console.log("🧹 Cleaning up tracker interval"); - stopInterval(); + console.log("🧹 Cleaning up tracker"); + stopIntervals(); }; - }, [isTracking, activeProjectLogId, projectId, projectName, dispatch, stopInterval]); + }, [ + isTracking, + activeProjectLogId, + projectId, + projectName, + stopIntervals, + dispatch, + debugCurrentLogs, + ]); return { - finalizeTracking: finalizeTrackingRef.current, - stopInterval, + finalizeAndSendLogs, + stopIntervals, setIsStopping: (value: boolean) => { isStoppingRef.current = value; - console.log(`πŸ”’ Stopping flag manually set to: ${value}`); - } + }, + debugCurrentLogs, }; }; \ No newline at end of file diff --git a/src/ui/layout/DashboardLayout.tsx b/src/ui/layout/DashboardLayout.tsx index c071a1c..0a64f69 100644 --- a/src/ui/layout/DashboardLayout.tsx +++ b/src/ui/layout/DashboardLayout.tsx @@ -39,6 +39,7 @@ import { canStartTracking } from "../utils/projectLimits"; import { useTheme } from "../helper/ThemeToggle"; import LogoutModal from "../modal/LogoutModal"; import { parseTimeStringToSeconds } from "../utils/timeParser"; +import { clearAllLogs, clearProjectLogs } from "../store/logsSlice"; interface User { id: string; @@ -100,6 +101,8 @@ const DashboardLayout = () => { const { projectsData, projectSummary, selectedProject, projectTrackingMap } = useSelector((state: any) => state.projects); + const { logsByProject } = useSelector((state: RootState) => state.logs); + const tracking = selectedProject ? projectTrackingMap[selectedProject.projectId] : null; @@ -173,7 +176,7 @@ const DashboardLayout = () => { }, [selectedProject]); // Initialize useActiveAppTracker and store finalizeTracking in ref - const { finalizeTracking, setIsStopping: setIsStoppingInTracker } = useActiveAppTracker( + const { finalizeAndSendLogs, setIsStopping: setIsStoppingInTracker } = useActiveAppTracker( isTracking, activeProjectLogId, selectedProject?.projectId ?? null, @@ -182,189 +185,131 @@ const DashboardLayout = () => { ); // Define handleStopTracking FIRST - const handleStopTracking = useCallback(async () => { - if (loadingStop || loadingStart) { - console.log("⏸️ Already processing, skipping..."); - return; - } + const handleStopTracking = useCallback(async () => { + if (loadingStop || loadingStart) { + return; + } - if (!selectedProject) { - console.warn("⚠️ No selected project to stop"); - return; - } + if (!selectedProject) { + return; + } - console.log("=".repeat(70)); - console.log("πŸ›‘ STOP TRACKING INITIATED"); - console.log("=".repeat(70)); - console.log(`πŸ“¦ Project: ${selectedProject.projectName}`); - console.log(`⏱️ Elapsed Time: ${elapsedTime}s`); + console.log("πŸ›‘ STOP TRACKING INITIATED"); - // Reset alert flag when stopping - limitAlertShownRef.current = false; + // Set loading state + setIsStopping(true); + setLoadingStop(true); - const currentStopTime = new Date().toISOString(); - const projectId = selectedProject.projectId; + const projectId = selectedProject.projectId; + const vaId = projectSummary.vaId || ""; - // βœ… CRITICAL: Set stopping flag IMMEDIATELY to block all new tab tracking - console.log("πŸ”’ Setting stopping flag..."); - if (setIsStoppingInTracker) { - setIsStoppingInTracker(true); - } + // STEP 1: Set stopping flag in tracker + if (setIsStoppingInTracker) { + setIsStoppingInTracker(true); + } - setIsStopping(true); - setLoadingStop(true); - handleSocketDisconnect(); + // STEP 2: Disconnect socket + handleSocketDisconnect(); - // STEP 1: Clear local timer to prevent further client-side updates - console.log("⏹️ STEP 1: Clearing local timer..."); - if (timerRef.current) { - clearInterval(timerRef.current); - timerRef.current = null; - console.log("βœ… Timer cleared"); - } + // STEP 3: Clear local timer + if (timerRef.current) { + clearInterval(timerRef.current); + timerRef.current = null; + } - // STEP 2: Send final PATCH with last active window data - console.log("πŸ“€ STEP 2: Sending final activity tracking..."); - if (stopTrackingRef.current) { - try { - await stopTrackingRef.current(); // This calls sendFinalPatch() in the hook - console.log("βœ… Final activity tracking completed"); - } catch (error) { - console.error("❌ Error during final tracking:", error); - } - } else { - console.log("⚠️ No tracking ref available"); - } - - // STEP 3: Send LOGOUT to backend - console.log("πŸšͺ STEP 3: Sending LOGOUT request..."); + // STEP 4: Finalize and send logs + console.log("πŸ“€ STEP 4: Finalizing and sending logs..."); + let logsSent = false; + if (finalizeAndSendLogs) { try { - const payload = { - vaId: projectSummary.vaId, - projectId: projectId, - type: "LOGOUT", - time: currentStopTime - }; - - console.log("πŸ“‘ Logout payload:", payload); - const res = await window.electron.stopProjectTracking(payload); - console.log("βœ… LOGOUT successful:", res); + logsSent = await finalizeAndSendLogs(vaId); // Pass vaId here + console.log(logsSent ? "βœ… Logs sent successfully" : "⚠️ Logs not sent"); } catch (error) { - console.error("❌ LOGOUT failed:", error); + console.error("❌ Error sending logs:", error); } + } - // STEP 4: Update Redux tracking state - console.log("πŸ”„ STEP 4: Updating Redux state..."); - dispatch( - stopTrackingReducer({ - projectId, - }) - ); + // STEP 5: Send LOGOUT to backend + console.log("πŸšͺ STEP 5: Sending LOGOUT request..."); + try { + const logoutPayload = { + vaId: vaId, + projectId: projectId, + type: "LOGOUT", + time: new Date().toISOString() + }; - // STEP 6: Update UI (restore window) - console.log("πŸͺŸ STEP 6: Restoring window..."); - await window.electron.toggleCloseBtn(true); - await window.electron.resizeWindow(500, 600); - toggleWindowSize.current = true; - console.log("βœ… Window restored"); + await window.electron.stopProjectTracking(logoutPayload); + console.log("βœ… LOGOUT successful"); + } catch (error) { + console.error("❌ LOGOUT failed:", error); + } - // Allow React to process the state update - await new Promise(resolve => setTimeout(resolve, 50)); + // STEP 6: Update Redux tracking state + dispatch(stopTrackingReducer({ projectId })); - // STEP 5: Refresh project list to get updated work hours from server - console.log("πŸ“Š STEP 5: Refreshing project list..."); - try { - const projectListRes = await window.electron.getProjectList(); - console.log("βœ… Project list fetched:", projectListRes); + // STEP 7: Refresh project list + try { + const projectListRes = await window.electron.getProjectList(); + if (projectListRes?.success && Array.isArray(projectListRes.data)) { + dispatch(setProjectsDataSlice(projectListRes.data)); + + dispatch(setProjectSummarySlice({ + totalProjects: projectListRes.totalProjects || 0, + activeProjects: projectListRes.activeProjects || 0, + awayProjects: projectListRes.awayProjects || 0, + totalWorkTime: projectListRes.totalWorkTime || "0h 0m 0s", + totalWorkMs: projectListRes.totalWorkMs || 0, + vaName: projectListRes.vaName || "", + vaId: projectListRes.vaId || "", + })); - if (projectListRes?.success && Array.isArray(projectListRes.data)) { - // Update projects data - dispatch(setProjectsDataSlice(projectListRes.data)); + // Update selected project + const updatedProject = projectListRes.data.find( + (p: Project) => p.projectId === projectId + ); - // Update project summary - dispatch( - setProjectSummarySlice({ - totalProjects: projectListRes.totalProjects || 0, - activeProjects: projectListRes.activeProjects || 0, - awayProjects: projectListRes.awayProjects || 0, - totalWorkTime: projectListRes.totalWorkTime || "0h 0m 0s", - totalWorkMs: projectListRes.totalWorkMs || 0, - vaName: projectListRes.vaName || "", - vaId: projectListRes.vaId || "", - }) - ); - - // Update selected project with fresh data from server - const updatedProject = projectListRes.data.find( - (p: Project) => p.projectId === projectId - ); - - if (updatedProject) { - dispatch(setSelectedProjectSlice(updatedProject)); - - // Sync client time with server time - const serverElapsedTime = parseTimeStringToSeconds(updatedProject.totalWorkTime); - - console.log("πŸ”„ Syncing with server time:", { - serverTime: updatedProject.totalWorkTime, - serverSeconds: serverElapsedTime, - clientSeconds: elapsedTime, - difference: serverElapsedTime - elapsedTime - }); - - // Force update the tracking map with server time - dispatch( - updateProjectTracking({ - projectId: updatedProject.projectId, - data: { - elapsedTime: serverElapsedTime, - accumulatedTime: serverElapsedTime, - isTracking: false, - isPaused: false, - startTime: null, - endTime: Date.now(), - pauseStartTime: null, - totalPausedTime: 0 - } - }) - ); - - console.log("βœ… Project synced with server"); - } + if (updatedProject) { + dispatch(setSelectedProjectSlice(updatedProject)); } - - - - } catch (error) { - console.error("❌ Error refreshing project list:", error); - } finally { - // Clear loading states - setLoadingStop(false); - setIsStopping(false); - - // βœ… Reset stopping flag for next session - if (setIsStoppingInTracker) { - setIsStoppingInTracker(false); - } - - console.log("=".repeat(70)); - console.log("πŸŽ‰ TRACKING STOPPED SUCCESSFULLY"); - console.log("=".repeat(70)); } - }, [ - selectedProject, - projectSummary, - elapsedTime, - dispatch, - loadingStop, - loadingStart, - setIsStoppingInTracker - ]); + } catch (error) { + console.error("❌ Error refreshing project list:", error); + } + + // STEP 8: Reset logs for this project + if (logsSent && projectId) { + dispatch(clearProjectLogs({ projectId })); + } + + // STEP 9: Restore window + await window.electron.toggleCloseBtn(true); + await window.electron.resizeWindow(500, 600); + toggleWindowSize.current = true; + + // STEP 10: Final cleanup + setLoadingStop(false); + setIsStopping(false); + + if (setIsStoppingInTracker) { + setIsStoppingInTracker(false); + } + + console.log("πŸŽ‰ TRACKING STOPPED SUCCESSFULLY"); +}, [ + selectedProject, + projectSummary, + dispatch, + loadingStop, + loadingStart, + setIsStoppingInTracker, + finalizeAndSendLogs, +]); // Update the ref whenever finalizeTracking changes - useEffect(() => { - stopTrackingRef.current = finalizeTracking; - }, [finalizeTracking]); + // useEffect(() => { + // stopTrackingRef.current = finalizeTracking; + // }, [finalizeTracking]); const socketRef = useRef(null); const intervalRef = useRef(null); @@ -404,6 +349,7 @@ const DashboardLayout = () => { const handleLogout = async () => { try { + dispatch(clearAllLogs()); const res = await window.electron.logout(); if (res) { dispatch(clearUser()); @@ -459,6 +405,11 @@ const DashboardLayout = () => { return; } + // Reset logs for this project if they exist + if (logsByProject[project.projectId]) { + dispatch(clearProjectLogs({ projectId: project.projectId })); + } + // CRITICAL FIX: Get the current accumulated time BEFORE starting timer const currentAccumulatedTime = selectedData?.accumulatedTime || parseTimeStringToSeconds(project.totalWorkTime); @@ -927,4 +878,6 @@ const DashboardLayout = () => { ); }; -export default DashboardLayout; \ No newline at end of file +export default DashboardLayout; + + diff --git a/src/ui/store/index.tsx b/src/ui/store/index.tsx index 18dc804..d7af0c6 100644 --- a/src/ui/store/index.tsx +++ b/src/ui/store/index.tsx @@ -4,6 +4,7 @@ import windowReducer from "./windowSlice"; import projectsReducer from "./projectSlice"; import userReducer from "./userSlice"; import profileReducer from "./profileSlice"; +import logsReducer from './logsSlice'; export const store = configureStore({ reducer: { @@ -11,6 +12,7 @@ export const store = configureStore({ projects: projectsReducer, user: userReducer, profile: profileReducer, + logs: logsReducer, }, }); diff --git a/src/ui/store/logsSlice.tsx b/src/ui/store/logsSlice.tsx new file mode 100644 index 0000000..3bec6f9 --- /dev/null +++ b/src/ui/store/logsSlice.tsx @@ -0,0 +1,182 @@ +// store/logsSlice.ts +import { createSlice, PayloadAction } from "@reduxjs/toolkit"; + +export interface LogEntry { + id: string; // Unique ID for each log entry + title: string; + appName: string; + url: string | null; + from: string; // UTC ISO string + to: string; // UTC ISO string + totalTime: number; // milliseconds +} + +export interface ProjectLogs { + projectId: string; + logId: string | null; + logs: LogEntry[]; // All collected logs during tracking + trackingStartTime: string | null; + lastWindowTitle: string | null; // Track last window title + lastWindowApp: string | null; // Track last window app +} + +interface LogsState { + logsByProject: Record; +} + +const initialState: LogsState = { + logsByProject: {}, +}; + +const logsSlice = createSlice({ + name: "logs", + initialState, + reducers: { + // Initialize logs for a project + initializeProjectLogs: ( + state, + action: PayloadAction<{ + projectId: string; + logId: string; + }> + ) => { + const { projectId, logId } = action.payload; + const nowUTC = new Date().toISOString(); + + // Always create fresh logs for new tracking session + state.logsByProject[projectId] = { + projectId, + logId, + logs: [], // Start with empty logs + trackingStartTime: nowUTC, + lastWindowTitle: null, + lastWindowApp: null, + }; + + console.log(`πŸ“ Initialized logs for project ${projectId} with logId: ${logId}`); + }, + + // Add or update window log + updateWindowLog: ( + state, + action: PayloadAction<{ + projectId: string; + title: string; + appName: string; + url?: string | null; + }> + ) => { + const { projectId, title, appName, url = null } = action.payload; + const projectLogs = state.logsByProject[projectId]; + + if (!projectLogs) { + console.warn(`Cannot update log: Project ${projectId} not initialized`); + return; + } + + const nowUTC = new Date().toISOString(); + const isSameWindow = + projectLogs.lastWindowTitle === title && + projectLogs.lastWindowApp === appName; + + // If same window as before, update the last log's end time + if (isSameWindow && projectLogs.logs.length > 0) { + const lastLog = projectLogs.logs[projectLogs.logs.length - 1]; + const startTime = new Date(lastLog.from); + const endTime = new Date(nowUTC); + lastLog.to = nowUTC; + lastLog.totalTime = endTime.getTime() - startTime.getTime(); + // console.log(`⏱️ Updated existing log: ${title} (${lastLog.totalTime}ms)`); + } + // If new window or first window + else { + // If there was a previous window, ensure it's properly ended + if (projectLogs.lastWindowTitle && projectLogs.logs.length > 0) { + const lastLog = projectLogs.logs[projectLogs.logs.length - 1]; + const endTime = new Date(nowUTC); + const startTime = new Date(lastLog.from); + lastLog.to = nowUTC; + lastLog.totalTime = endTime.getTime() - startTime.getTime(); + console.log( + `βœ… Finalized previous window: ${lastLog.title} (${lastLog.totalTime}ms)` + ); + } + + // Create new log entry + const newLog: LogEntry = { + id: `log_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, + title, + appName, + url, + from: nowUTC, + to: nowUTC, // Will be updated continuously + totalTime: 0, // Starts at 0 + }; + + projectLogs.logs.push(newLog); + console.log(`πŸ“ Started new window: ${title} on ${appName}`); + } + + // Update last window tracking + projectLogs.lastWindowTitle = title; + projectLogs.lastWindowApp = appName; + }, + + // Clear project logs + clearProjectLogs: ( + state, + action: PayloadAction<{ + projectId: string; + }> + ) => { + const { projectId } = action.payload; + delete state.logsByProject[projectId]; + console.log(`🧹 Cleared logs for project ${projectId}`); + }, + + // Clear all logs + clearAllLogs: (state) => { + state.logsByProject = {}; + console.log("🧹 Cleared all logs"); + }, + + // Debug: Get logs state + debugLogsState: ( + state, + action: PayloadAction<{ + projectId: string; + }> + ) => { + const { projectId } = action.payload; + const projectLogs = state.logsByProject[projectId]; + + if (!projectLogs) { + console.log(`πŸ“‹ No logs found for project ${projectId}`); + return; + } + + console.log(`πŸ“‹ Logs State for ${projectId}:`, { + totalLogs: projectLogs.logs.length, + lastWindow: projectLogs.lastWindowTitle, + logs: projectLogs.logs.map((log) => ({ + title: + log.title.substring(0, 40) + (log.title.length > 40 ? "..." : ""), + appName: log.appName, + from: new Date(log.from).toLocaleTimeString(), + to: new Date(log.to).toLocaleTimeString(), + duration: log.totalTime, + })), + }); + }, + }, +}); + +export const { + initializeProjectLogs, + updateWindowLog, + clearProjectLogs, + clearAllLogs, + debugLogsState, +} = logsSlice.actions; + +export default logsSlice.reducer; \ No newline at end of file diff --git a/types.d.ts b/types.d.ts index d080cd6..9464e15 100644 --- a/types.d.ts +++ b/types.d.ts @@ -142,6 +142,24 @@ interface RegionResponse { } +interface TrackingLog { + title: string; + appName: string; + url: string; + from: string; + to: string; + totalTime: number; +} + +interface TrackingPayload { + vaId: string; + projectId: string; + logId: string; + logs: TrackingLog[]; +} + + + interface Window { electron: { login: (credentials: { email: string; password: string }) => Promise; @@ -171,14 +189,7 @@ interface Window { type: string; time: string }) => Promise; - trackingTabsOpeningClosing: (data: { - id?: string; - logId: string; - projectId: string; - title: string; - appName: string; - }) => Promise; - + trackingTabsOpeningClosing: (data: TrackingPayload) => Promise; getProjectLogs: ( projectId: string, fromDate?: string, From d71ac936fa040914b2da1bfc541087ad3fa148e1 Mon Sep 17 00:00:00 2001 From: BhagwanSingh Date: Fri, 12 Dec 2025 12:07:14 +0530 Subject: [PATCH 07/14] a --- src/assets/splash.html | 275 +++++++++++++++++++++++++++++- src/ui/layout/DashboardLayout.tsx | 2 +- 2 files changed, 270 insertions(+), 7 deletions(-) diff --git a/src/assets/splash.html b/src/assets/splash.html index ac2e51a..92a9805 100644 --- a/src/assets/splash.html +++ b/src/assets/splash.html @@ -1,5 +1,6 @@ + @@ -55,8 +60,266 @@
- + + \ No newline at end of file diff --git a/src/ui/layout/DashboardLayout.tsx b/src/ui/layout/DashboardLayout.tsx index c071a1c..62822b0 100644 --- a/src/ui/layout/DashboardLayout.tsx +++ b/src/ui/layout/DashboardLayout.tsx @@ -116,7 +116,7 @@ const DashboardLayout = () => { { label: "Schedule", path: "/dashboard/schedule", icon: }, { label: "Pays Slips", path: "/dashboard/pays-slips", icon: }, { label: "Settings", path: "/dashboard/settings", icon: }, - { label: "VPN", path: "/dashboard/vpn", icon: }, + // { label: "VPN", path: "/dashboard/vpn", icon: }, { label: "Help & Support", path: "/dashboard/help-support", icon: }, ]; From 2306e54515977060007f71e4df5461223adc878d Mon Sep 17 00:00:00 2001 From: BhagwanSingh Date: Tue, 16 Dec 2025 16:24:11 +0530 Subject: [PATCH 08/14] VPN Start --- src/ui/pages/Vpn/VpnGuard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/pages/Vpn/VpnGuard.tsx b/src/ui/pages/Vpn/VpnGuard.tsx index 609dc36..40823d2 100644 --- a/src/ui/pages/Vpn/VpnGuard.tsx +++ b/src/ui/pages/Vpn/VpnGuard.tsx @@ -7,7 +7,7 @@ interface Props { const VpnGuard: React.FC = ({ children }) => { - const VPN_API = "http://10.0.0.2:3000"; + const VPN_API = "http://10.0.0.1:3000"; const DEFAULT_API = "https://quality-friendly-goldfish.ngrok-free.app" const [checking, setChecking] = useState(true); From d7128000beca24035b74a4c1db2c6030fed831b2 Mon Sep 17 00:00:00 2001 From: BhagwanSingh Date: Wed, 17 Dec 2025 15:21:43 +0530 Subject: [PATCH 09/14] VPN Code UPdate --- resources/wireguard/windows/wg0.conf | 7 +- src/electron/api/axiosClient.ts | 2 +- src/electron/api/baseURL.ts | 4 +- src/electron/api/regionCheck.ts | 5 +- src/electron/vpn.ts | 91 ++++++++++++++++--- src/ui/layout/DashboardLayout.tsx | 2 +- src/ui/main.tsx | 15 +++ src/ui/pages/Vpn/VpnGuard.tsx | 21 ++++- ....timestamp-1765963658478-f34f24a548cda.mjs | 19 ++++ 9 files changed, 138 insertions(+), 28 deletions(-) create mode 100644 vite.config.ts.timestamp-1765963658478-f34f24a548cda.mjs diff --git a/resources/wireguard/windows/wg0.conf b/resources/wireguard/windows/wg0.conf index fa2a861..fe4bde9 100644 --- a/resources/wireguard/windows/wg0.conf +++ b/resources/wireguard/windows/wg0.conf @@ -1,10 +1,11 @@ + [Interface] -PrivateKey = CEzCd25ufxbOjvRhMqLfkcdP/OziG5i6OkXCfMZrI1I= -Address = 10.0.0.2/32 +PrivateKey = EA2j6mOQB17w497cZS4B91m/xDzEcGz9lU3jKtc1wU8= +Address = 10.0.0.27/32 DNS = 1.1.1.1 [Peer] PublicKey = jG+7H5thEiNjxY1XqI1R9oyMAMTIegLwE3tZkS1Rj2U= Endpoint = 35.95.209.161:51820 -AllowedIPs = 10.0.0.0/32 +AllowedIPs = 10.0.0.0/24 PersistentKeepalive = 25 diff --git a/src/electron/api/axiosClient.ts b/src/electron/api/axiosClient.ts index bb23df0..3cd6426 100644 --- a/src/electron/api/axiosClient.ts +++ b/src/electron/api/axiosClient.ts @@ -18,7 +18,7 @@ const httpsAgent = new https.Agent({ console.log('Base_URL$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$' ,Base_URL) const api: AxiosInstance = axios.create({ - // baseURL: 'https://quality-friendly-goldfish.ngrok-free.app/api/v1/va', // πŸ” your backend + // baseURL: 'https://portal-sf.docva.com/api/v1/va', // πŸ” your backend baseURL: `${Base_URL}/api/v1/va`, httpsAgent // timeout: 10000, diff --git a/src/electron/api/baseURL.ts b/src/electron/api/baseURL.ts index 67478cd..2a33d5a 100644 --- a/src/electron/api/baseURL.ts +++ b/src/electron/api/baseURL.ts @@ -1,5 +1,5 @@ -// let CURRENT_BASE_URL = "https://quality-friendly-goldfish.ngrok-free.app/api/v1/va"; -let CURRENT_BASE_URL = "https://quality-friendly-goldfish.ngrok-free.app"; +// let CURRENT_BASE_URL = "https://portal-sf.docva.com/api/v1/va"; +let CURRENT_BASE_URL = "https://portal-sf.docva.com"; export const getBaseURL = () => CURRENT_BASE_URL; export const setBaseURL = (url: string) => { diff --git a/src/electron/api/regionCheck.ts b/src/electron/api/regionCheck.ts index b545c8f..5a5912b 100644 --- a/src/electron/api/regionCheck.ts +++ b/src/electron/api/regionCheck.ts @@ -8,13 +8,14 @@ const httpsAgent = new https.Agent({ rejectUnauthorized: false }); export const checkRegion = async () => { const baseURL = await getBaseURL(); - console.log("checkRegion@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" ,baseURL) + console.log("checkRegion@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", baseURL) try { const res = await axios.get(`${baseURL}/health`, { headers: { "ngrok-skip-browser-warning": "true" }, - httpsAgent + httpsAgent, + timeout: 4000 }); console.log(res , "checkRegion") diff --git a/src/electron/vpn.ts b/src/electron/vpn.ts index 19596b9..9efba20 100644 --- a/src/electron/vpn.ts +++ b/src/electron/vpn.ts @@ -3,6 +3,7 @@ import { exec, execFile } from "child_process"; import fs from "fs"; import os from "os"; import path from "path"; +import axios from "axios"; // // ------------------ HELPERS ------------------ @@ -51,20 +52,72 @@ function getWireGuardInstallerPath(): string { // OS-specific wg0.conf -function getConfigPath(): string { +// function getConfigPath(): string { +// const base = isDev() +// ? path.join(process.cwd(), "resources", "wireguard") +// : path.join(process.resourcesPath, "wireguard"); + +// if (process.platform === "win32") { +// return path.join(base, "windows", "wg0.conf"); +// } + +// if (process.platform === "darwin") { +// return path.join(base, "macos", "wg0.conf"); +// } + +// throw new Error("Unsupported OS"); +// } + +export async function getConfigPath(): Promise { + if (process.platform !== "win32") { + throw new Error("Only Windows supported for dynamic WireGuard config"); + } + console.log("Calling http://35.95.209.161/api/v1/vpn/connect") + // 1️⃣ Call API to get dynamic config + let response = null; + try { + response = await axios.post( + "http://35.95.209.161/api/v1/vpn/connect", + {}, + { timeout: 15000 } + ); + } catch (err: any) { + console.error("VPN API error:", { + message: err.message, + code: err.code, + response: err.response?.data, + }); + throw err; + } + console.log("Called http://35.95.209.161/api/v1/vpn/connect", JSON.stringify(response.data)); + + if (!response.data?.success || !response.data.clientConf) { + throw new Error("Invalid VPN config response"); + } + + const clientConf: string = response.data.clientConf; + + // 2️⃣ Decide base directory (same idea as your existing logic) const base = isDev() - ? path.join(process.cwd(), "resources", "wireguard") - : path.join(process.resourcesPath, "wireguard"); + ? path.join(process.cwd(), "resources", "wireguard", "windows") + : path.join(process.resourcesPath, "wireguard", "windows"); - if (process.platform === "win32") { - return path.join(base, "windows", "wg0.conf"); - } + // Ensure directory exists + fs.mkdirSync(base, { recursive: true }); - if (process.platform === "darwin") { - return path.join(base, "macos", "wg0.conf"); - } + // 3️⃣ Create temp / runtime conf file + // IMPORTANT: name must stay stable (WireGuard service name depends on it) + const confPath = path.join(base, "wg0.conf"); - throw new Error("Unsupported OS"); + // 4️⃣ Write config to file + fs.writeFileSync(confPath, clientConf, { + encoding: "utf-8", + mode: 0o600, // secure permissions + }); + + console.log("VPN config written to:", confPath); + // 5️⃣ Return path (same contract as before) + return confPath; } // Detect installed WireGuard.exe on Windows @@ -145,16 +198,24 @@ async function ensureWireGuardInstalled(): Promise { async function connectWindows(): Promise { + + try { + const response = await axios.get('http://35.95.209.161') + console.log("Response from test API:", response.data); + } catch (error) { + console.error("Error connecting to test API:", error); + } + const cli = await ensureWireGuardInstalled(); - const conf = getConfigPath(); + const conf = await getConfigPath(); // Disable GUI popup and silent mode (ignore errors) - await execPromise(`reg add "HKCU\\Software\\WireGuard" /v HideTrayIcon /t REG_DWORD /d 1 /f`).catch(() => {}); - await execPromise(`reg add "HKCU\\Software\\WireGuard" /v SuppressTray /t REG_DWORD /d 1 /f`).catch(() => {}); - await execPromise(`reg delete "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run" /v WireGuard /f`).catch(() => {}); + await execPromise(`reg add "HKCU\\Software\\WireGuard" /v HideTrayIcon /t REG_DWORD /d 1 /f`).catch(() => { }); + await execPromise(`reg add "HKCU\\Software\\WireGuard" /v SuppressTray /t REG_DWORD /d 1 /f`).catch(() => { }); + await execPromise(`reg delete "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run" /v WireGuard /f`).catch(() => { }); // Start background service - await execPromise(`sc start WireGuardManager`).catch(() => {}); + await execPromise(`sc start WireGuardManager`).catch(() => { }); return new Promise((resolve, reject) => { execFile(cli, ["/installtunnelservice", conf], (err) => { diff --git a/src/ui/layout/DashboardLayout.tsx b/src/ui/layout/DashboardLayout.tsx index 62822b0..378d729 100644 --- a/src/ui/layout/DashboardLayout.tsx +++ b/src/ui/layout/DashboardLayout.tsx @@ -590,7 +590,7 @@ const DashboardLayout = () => { if (!selectedProject?.projectId) return console.log(selectedProject?.projectId, "SelectedProject ID") - socketRef.current = io("https://quality-friendly-goldfish.ngrok-free.app", { + socketRef.current = io("https://portal-sf.docva.com", { transports: ["websocket"], secure: true, rejectUnauthorized: false, diff --git a/src/ui/main.tsx b/src/ui/main.tsx index 5a91adf..4ec36b4 100644 --- a/src/ui/main.tsx +++ b/src/ui/main.tsx @@ -6,6 +6,21 @@ import { Provider } from "react-redux"; import { store } from "./store"; import { ToastProvider } from './helper/ToastContext'; +// renderer only +window.addEventListener("online", (e) => { + console.log("Network online (ignored)"); + e.preventDefault(); +}); + +window.addEventListener("offline", (e) => { + console.log("Network offline (ignored)"); + e.preventDefault(); +}); + +// Extra safety +window.onbeforeunload = () => false; + + ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( diff --git a/src/ui/pages/Vpn/VpnGuard.tsx b/src/ui/pages/Vpn/VpnGuard.tsx index 40823d2..5100825 100644 --- a/src/ui/pages/Vpn/VpnGuard.tsx +++ b/src/ui/pages/Vpn/VpnGuard.tsx @@ -8,7 +8,7 @@ interface Props { const VpnGuard: React.FC = ({ children }) => { const VPN_API = "http://10.0.0.1:3000"; - const DEFAULT_API = "https://quality-friendly-goldfish.ngrok-free.app" + const DEFAULT_API = "https://portal-sf.docva.com" const [checking, setChecking] = useState(true); const [vpnConnected, setVpnConnected] = useState(true); @@ -20,9 +20,12 @@ const VpnGuard: React.FC = ({ children }) => { const res = await window.electron?.checkRegion(); console.log("VpnGuard", res); - setVpnConnected(false); - return - + if (res.status === 200) { + setVpnConnected(true); + } else { + setVpnConnected(false); + return; + } // setVpnConnected(res?.success === true); } catch (error) { console.error("VPN check failed:", error); @@ -58,6 +61,16 @@ const VpnGuard: React.FC = ({ children }) => { } } catch (error) { console.log(error); + + 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"); + } } finally { setChecking(false); } diff --git a/vite.config.ts.timestamp-1765963658478-f34f24a548cda.mjs b/vite.config.ts.timestamp-1765963658478-f34f24a548cda.mjs new file mode 100644 index 0000000..dcc3c93 --- /dev/null +++ b/vite.config.ts.timestamp-1765963658478-f34f24a548cda.mjs @@ -0,0 +1,19 @@ +// vite.config.ts +import { defineConfig } from "file:///C:/Users/user/OneDrive/Desktop/electron-main/node_modules/vite/dist/node/index.js"; +import react from "file:///C:/Users/user/OneDrive/Desktop/electron-main/node_modules/@vitejs/plugin-react/dist/index.js"; +import tailwindcss from "file:///C:/Users/user/OneDrive/Desktop/electron-main/node_modules/@tailwindcss/vite/dist/index.mjs"; +var vite_config_default = defineConfig({ + plugins: [react(), tailwindcss()], + base: "./", + build: { + outDir: "dist-react" + }, + server: { + port: 5123, + strictPort: true + } +}); +export { + vite_config_default as default +}; +//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCJDOlxcXFxVc2Vyc1xcXFx1c2VyXFxcXE9uZURyaXZlXFxcXERlc2t0b3BcXFxcZWxlY3Ryb24tbWFpblwiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9maWxlbmFtZSA9IFwiQzpcXFxcVXNlcnNcXFxcdXNlclxcXFxPbmVEcml2ZVxcXFxEZXNrdG9wXFxcXGVsZWN0cm9uLW1haW5cXFxcdml0ZS5jb25maWcudHNcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfaW1wb3J0X21ldGFfdXJsID0gXCJmaWxlOi8vL0M6L1VzZXJzL3VzZXIvT25lRHJpdmUvRGVza3RvcC9lbGVjdHJvbi1tYWluL3ZpdGUuY29uZmlnLnRzXCI7aW1wb3J0IHsgZGVmaW5lQ29uZmlnIH0gZnJvbSAndml0ZSc7XHJcbmltcG9ydCByZWFjdCBmcm9tICdAdml0ZWpzL3BsdWdpbi1yZWFjdCc7XHJcbmltcG9ydCB0YWlsd2luZGNzcyBmcm9tICdAdGFpbHdpbmRjc3Mvdml0ZSdcclxuXHJcbi8vIGh0dHBzOi8vdml0ZWpzLmRldi9jb25maWcvXHJcbmV4cG9ydCBkZWZhdWx0IGRlZmluZUNvbmZpZyh7XHJcbiAgcGx1Z2luczogW3JlYWN0KCksdGFpbHdpbmRjc3MoKV0sXHJcbiAgYmFzZTogJy4vJyxcclxuICBidWlsZDoge1xyXG4gICAgb3V0RGlyOiAnZGlzdC1yZWFjdCcsXHJcbiAgfSxcclxuICBzZXJ2ZXI6IHtcclxuICAgIHBvcnQ6IDUxMjMsXHJcbiAgICBzdHJpY3RQb3J0OiB0cnVlLFxyXG4gIH0sXHJcbn0pO1xyXG4iXSwKICAibWFwcGluZ3MiOiAiO0FBQWtVLFNBQVMsb0JBQW9CO0FBQy9WLE9BQU8sV0FBVztBQUNsQixPQUFPLGlCQUFpQjtBQUd4QixJQUFPLHNCQUFRLGFBQWE7QUFBQSxFQUMxQixTQUFTLENBQUMsTUFBTSxHQUFFLFlBQVksQ0FBQztBQUFBLEVBQy9CLE1BQU07QUFBQSxFQUNOLE9BQU87QUFBQSxJQUNMLFFBQVE7QUFBQSxFQUNWO0FBQUEsRUFDQSxRQUFRO0FBQUEsSUFDTixNQUFNO0FBQUEsSUFDTixZQUFZO0FBQUEsRUFDZDtBQUNGLENBQUM7IiwKICAibmFtZXMiOiBbXQp9Cg== From c03c89d613c5eb80ffdd2009e03f29595103d188 Mon Sep 17 00:00:00 2001 From: JaiPandey425 Date: Wed, 17 Dec 2025 15:23:11 +0530 Subject: [PATCH 10/14] handleStop tracking fixed time --- resources/wireguard/windows/wg0.conf | 6 +- src/electron/api/regionCheck.ts | 2 +- src/electron/ipc/ipcHandler.ts | 2 +- src/ui/hooks/useActiveAppTracker.tsx | 184 ++++++------- src/ui/layout/DashboardLayout.tsx | 275 ++++++++++++-------- src/ui/pages/Vpn/VpnGuard.tsx | 2 +- src/ui/store/logsSlice.tsx | 372 ++++++++++++++++----------- types.d.ts | 1 + 8 files changed, 462 insertions(+), 382 deletions(-) diff --git a/resources/wireguard/windows/wg0.conf b/resources/wireguard/windows/wg0.conf index fa2a861..b2250e4 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/32 +Address = 10.0.0.2/24 DNS = 1.1.1.1 [Peer] PublicKey = jG+7H5thEiNjxY1XqI1R9oyMAMTIegLwE3tZkS1Rj2U= +AllowedIPs = 10.0.0.0/24 Endpoint = 35.95.209.161:51820 -AllowedIPs = 10.0.0.0/32 -PersistentKeepalive = 25 +PersistentKeepalive = 25 \ No newline at end of file diff --git a/src/electron/api/regionCheck.ts b/src/electron/api/regionCheck.ts index b545c8f..7134538 100644 --- a/src/electron/api/regionCheck.ts +++ b/src/electron/api/regionCheck.ts @@ -8,7 +8,7 @@ const httpsAgent = new https.Agent({ rejectUnauthorized: false }); export const checkRegion = async () => { const baseURL = await getBaseURL(); - console.log("checkRegion@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" ,baseURL) + console.log("Lin 11 BaseURL CHECK@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" ,baseURL) try { diff --git a/src/electron/ipc/ipcHandler.ts b/src/electron/ipc/ipcHandler.ts index 06eb15a..f13f3d4 100644 --- a/src/electron/ipc/ipcHandler.ts +++ b/src/electron/ipc/ipcHandler.ts @@ -172,7 +172,7 @@ ipcMain.handle("tracking-tabs-opening-and-closing", async (event, payload) => { const response = await api.post("/log/project/track", payload); - console.log("🟩 Log track API success:", response.data); + console.log("🟩 Log track API success:", response); return { success: true, diff --git a/src/ui/hooks/useActiveAppTracker.tsx b/src/ui/hooks/useActiveAppTracker.tsx index 8d24af1..2993c5e 100644 --- a/src/ui/hooks/useActiveAppTracker.tsx +++ b/src/ui/hooks/useActiveAppTracker.tsx @@ -70,128 +70,98 @@ export const useActiveAppTracker = ( }, [projectId, dispatch]); // Finalize and send all logs when stopping tracking - const finalizeAndSendLogs = useCallback( + // hooks/useActiveAppTracker.tsx - finalizeAndSendLogs function +const finalizeAndSendLogs = useCallback( async (vaId: string): Promise => { - // console.log("=".repeat(60)); - console.log(" FINALIZING AND SENDING ALL LOGS"); - // console.log("=".repeat(60)); + console.log(" FINALIZING AND SENDING ALL LOGS"); - // Set stopping flag - isStoppingRef.current = true; + // Set stopping flag + isStoppingRef.current = true; - // Stop intervals immediately - stopIntervals(); + // Stop intervals immediately + stopIntervals(); - if (!activeProjectLogId || !projectId) { - console.log("⚠️ Skipping β€” no active project"); - return false; - } - - const projectLogs = logsByProject[projectId]; - if (!projectLogs || projectLogs.logs.length === 0) { - console.log("ℹ️ No logs found for project", projectId); - return true; - } - - try { - const nowUTC = new Date().toISOString(); - - // Finalize the last log entry - const allLogs = [...projectLogs.logs]; - // if (allLogs.length > 0) { - // const lastLog = allLogs[allLogs.length - 1]; - // const endTime = new Date(nowUTC); - // const startTime = new Date(lastLog.from); - // lastLog.to = nowUTC; - // lastLog.totalTime = endTime.getTime() - startTime.getTime(); - // console.log(`πŸ›‘ Finalized last window: ${lastLog.title} (${lastLog.totalTime}ms)`); - // } - - // Filter out logs with very short duration (less than 1 second) - const validLogs = allLogs.filter((log) => { - const duration = new Date(log.to).getTime() - new Date(log.from).getTime(); - return duration >= 1000; // Minimum 1 second - }); - - console.log(`πŸ“Š Total logs: ${allLogs.length}, Valid logs (β‰₯1s): ${validLogs.length}`); - - if (validLogs.length === 0) { - console.log("ℹ️ No valid logs to send"); - dispatch(clearProjectLogs({ projectId })); - return true; + if (!activeProjectLogId || !projectId) { + console.log("⚠️ Skipping β€” no active project"); + return false; } - // Prepare payload with all valid logs - const payload = { - vaId: vaId || "", - projectId, - logId: activeProjectLogId, - logs: allLogs.map((log) => ({ - title: log.title, - appName: log.appName, - url: log.url ?? "", - from: log.from, - to: log.to, - totalTime: log.totalTime, - })), - }; + const projectLogs = logsByProject[projectId]; + if (!projectLogs || projectLogs.logs.length === 0) { + console.log("ℹ️ No logs found for project", projectId); + return true; + } - console.log("πŸ“€ Payload to send:", payload); - console.log("πŸ“Š Sending logs summary:", { - projectId, - projectName, - logCount: validLogs.length, - totalTime: validLogs.reduce((sum, log) => sum + log.totalTime, 0), - firstLog: validLogs[0] - ? { - title: validLogs[0].title.substring(0, 40), - appName: validLogs[0].appName, - duration: validLogs[0].totalTime, - from: new Date(validLogs[0].from).toLocaleTimeString(), - to: new Date(validLogs[0].to).toLocaleTimeString(), - } - : null, - lastLog: validLogs[validLogs.length - 1] - ? { - title: validLogs[validLogs.length - 1].title.substring(0, 40), - appName: validLogs[validLogs.length - 1].appName, - duration: validLogs[validLogs.length - 1].totalTime, - from: new Date(validLogs[validLogs.length - 1].from).toLocaleTimeString(), - to: new Date(validLogs[validLogs.length - 1].to).toLocaleTimeString(), - } - : null, - }); + try { + // Create a DEEP copy of the logs + const allLogs = JSON.parse(JSON.stringify(projectLogs.logs)); - let res = window.electron.trackingTabsOpeningClosing(payload) + // Filter out logs with very short duration (less than 1000ms = 1 second) + const validLogs = allLogs.filter((log:any) => { + return log.totalTime >= 1000; // Minimum 1 second (1000ms) + }); - console.log("Tracking response:", res); - dispatch(clearProjectLogs({ projectId })); + console.log(`πŸ“Š Total logs: ${allLogs.length}, Valid logs (β‰₯1000ms): ${validLogs.length}`); - // Cleanup refs - lastActiveRef.current = null; - lastTitleRef.current = null; - lastAppNameRef.current = null; - limitCheckedRef.current = false; + if (validLogs.length === 0) { + console.log("ℹ️ No valid logs to send"); + dispatch(clearProjectLogs({ projectId })); + return true; + } - console.log("βœ… Logs sent and cleared successfully"); - console.log("=".repeat(60)); + // Prepare payload with all valid logs - send directly as stored + const payload = { + vaId: vaId || "", + projectId, + logId: activeProjectLogId, + logs: validLogs, // Already formatted correctly + }; - return true; - } catch (error) { - const errorMsg = error instanceof Error ? error.message : "Unknown error"; - console.error("❌ Error sending logs:", error); - console.log("=".repeat(60)); - return false; - } + console.log("πŸ“€ Payload to send:", payload); + // console.log("πŸ“Š Sending logs summary:", { + // projectId, + // projectName, + // logCount: validLogs.length, + // totalTimeMs: validLogs.reduce((sum, log) => sum + log.totalTime, 0), + // totalTimeSec: validLogs.reduce((sum, log) => sum + Math.round(log.totalTime / 1000), 0), + // sampleLog: validLogs[0] ? { + // title: validLogs[0].title.substring(0, 40), + // from: validLogs[0].from, + // to: validLogs[0].to, + // durationMs: validLogs[0].totalTime, + // } : null, + // }); + + let res = window.electron.trackingTabsOpeningClosing(payload); + console.log("Tracking response:", res); + + dispatch(clearProjectLogs({ projectId })); + + // Cleanup refs + lastActiveRef.current = null; + lastTitleRef.current = null; + lastAppNameRef.current = null; + limitCheckedRef.current = false; + + console.log("βœ… Logs sent and cleared successfully"); + console.log("=".repeat(60)); + + return true; + } catch (error) { + const errorMsg = error instanceof Error ? error.message : "Unknown error"; + console.error("❌ Error sending logs:", error); + console.log("=".repeat(60)); + return false; + } }, [ - activeProjectLogId, - projectId, - logsByProject, - stopIntervals, - dispatch, + activeProjectLogId, + projectId, + logsByProject, + stopIntervals, + dispatch, ] - ); +); // Main tracking logic - runs every 5 seconds useEffect(() => { diff --git a/src/ui/layout/DashboardLayout.tsx b/src/ui/layout/DashboardLayout.tsx index 0a64f69..2432106 100644 --- a/src/ui/layout/DashboardLayout.tsx +++ b/src/ui/layout/DashboardLayout.tsx @@ -101,7 +101,7 @@ const DashboardLayout = () => { const { projectsData, projectSummary, selectedProject, projectTrackingMap } = useSelector((state: any) => state.projects); - const { logsByProject } = useSelector((state: RootState) => state.logs); + const { logsByProject } = useSelector((state: RootState) => state.logs); const tracking = selectedProject ? projectTrackingMap[selectedProject.projectId] @@ -176,7 +176,7 @@ const DashboardLayout = () => { }, [selectedProject]); // Initialize useActiveAppTracker and store finalizeTracking in ref - const { finalizeAndSendLogs, setIsStopping: setIsStoppingInTracker } = useActiveAppTracker( + const { finalizeAndSendLogs, setIsStopping: setIsStoppingInTracker } = useActiveAppTracker( isTracking, activeProjectLogId, selectedProject?.projectId ?? null, @@ -185,126 +185,177 @@ const DashboardLayout = () => { ); // Define handleStopTracking FIRST - const handleStopTracking = useCallback(async () => { - if (loadingStop || loadingStart) { - return; - } - - if (!selectedProject) { - return; - } - - console.log("πŸ›‘ STOP TRACKING INITIATED"); - - // Set loading state - setIsStopping(true); - setLoadingStop(true); - - const projectId = selectedProject.projectId; - const vaId = projectSummary.vaId || ""; - - // STEP 1: Set stopping flag in tracker - if (setIsStoppingInTracker) { - setIsStoppingInTracker(true); - } - - // STEP 2: Disconnect socket - handleSocketDisconnect(); - - // STEP 3: Clear local timer - if (timerRef.current) { - clearInterval(timerRef.current); - timerRef.current = null; - } - - // STEP 4: Finalize and send logs - console.log("πŸ“€ STEP 4: Finalizing and sending logs..."); - let logsSent = false; - if (finalizeAndSendLogs) { - try { - logsSent = await finalizeAndSendLogs(vaId); // Pass vaId here - console.log(logsSent ? "βœ… Logs sent successfully" : "⚠️ Logs not sent"); - } catch (error) { - console.error("❌ Error sending logs:", error); + const handleStopTracking = useCallback(async () => { + if (loadingStop || loadingStart) { + return; } - } - // STEP 5: Send LOGOUT to backend - console.log("πŸšͺ STEP 5: Sending LOGOUT request..."); - try { - const logoutPayload = { - vaId: vaId, - projectId: projectId, - type: "LOGOUT", - time: new Date().toISOString() - }; + if (!selectedProject) { + return; + } - await window.electron.stopProjectTracking(logoutPayload); - console.log("βœ… LOGOUT successful"); - } catch (error) { - console.error("❌ LOGOUT failed:", error); - } + console.log("πŸ›‘ STOP TRACKING INITIATED"); - // STEP 6: Update Redux tracking state - dispatch(stopTrackingReducer({ projectId })); + // Reset alert flag when stopping + limitAlertShownRef.current = false; - // STEP 7: Refresh project list - try { - const projectListRes = await window.electron.getProjectList(); - if (projectListRes?.success && Array.isArray(projectListRes.data)) { - dispatch(setProjectsDataSlice(projectListRes.data)); - - dispatch(setProjectSummarySlice({ - totalProjects: projectListRes.totalProjects || 0, - activeProjects: projectListRes.activeProjects || 0, - awayProjects: projectListRes.awayProjects || 0, - totalWorkTime: projectListRes.totalWorkTime || "0h 0m 0s", - totalWorkMs: projectListRes.totalWorkMs || 0, - vaName: projectListRes.vaName || "", - vaId: projectListRes.vaId || "", - })); - // Update selected project - const updatedProject = projectListRes.data.find( - (p: Project) => p.projectId === projectId - ); + const currentStopTime = new Date().toISOString(); + const projectId = selectedProject.projectId; + const vaId = projectSummary.vaId || ""; - if (updatedProject) { - dispatch(setSelectedProjectSlice(updatedProject)); + // STEP 1: Set stopping flag in tracker + if (setIsStoppingInTracker) { + setIsStoppingInTracker(true); + } + + // Set loading state + setIsStopping(true); + setLoadingStop(true); + + // STEP 2: Disconnect socket + handleSocketDisconnect(); + + // STEP 3: Clear local timer + if (timerRef.current) { + clearInterval(timerRef.current); + timerRef.current = null; + } + + // STEP 4: Finalize and send logs + console.log("πŸ“€ STEP 4: Finalizing and sending logs..."); + let logsSent = false; + if (finalizeAndSendLogs) { + try { + logsSent = await finalizeAndSendLogs(vaId); // Pass vaId here + console.log(logsSent ? "βœ… Logs sent successfully" : "⚠️ Logs not sent"); + } catch (error) { + console.error("❌ Error sending logs:", error); } } - } catch (error) { - console.error("❌ Error refreshing project list:", error); - } - // STEP 8: Reset logs for this project - if (logsSent && projectId) { - dispatch(clearProjectLogs({ projectId })); - } + // STEP 5: Send LOGOUT to backend + console.log("πŸšͺ STEP 5: Sending LOGOUT request..."); + try { + const logoutPayload = { + vaId: vaId, + logId: activeProjectLogId, + projectId: projectId, + type: "LOGOUT", + time: currentStopTime + }; - // STEP 9: Restore window - await window.electron.toggleCloseBtn(true); - await window.electron.resizeWindow(500, 600); - toggleWindowSize.current = true; + await window.electron.stopProjectTracking(logoutPayload); + console.log("βœ… LOGOUT successful"); + } catch (error) { + console.error("❌ LOGOUT failed:", error); + } - // STEP 10: Final cleanup - setLoadingStop(false); - setIsStopping(false); - - if (setIsStoppingInTracker) { - setIsStoppingInTracker(false); - } + // STEP 6: Update Redux tracking state + dispatch(stopTrackingReducer({ projectId })); - console.log("πŸŽ‰ TRACKING STOPPED SUCCESSFULLY"); -}, [ - selectedProject, - projectSummary, - dispatch, - loadingStop, - loadingStart, - setIsStoppingInTracker, - finalizeAndSendLogs, -]); + await window.electron.toggleCloseBtn(true); + await window.electron.resizeWindow(500, 600); + toggleWindowSize.current = true; + + // Allow React to process the state update + await new Promise(resolve => setTimeout(resolve, 50)); + + // STEP 7: Refresh project list + try { + const projectListRes = await window.electron.getProjectList(); + console.log("βœ… Project list fetched:", projectListRes); + + if (projectListRes?.success && Array.isArray(projectListRes.data)) { + // Update projects data + dispatch(setProjectsDataSlice(projectListRes.data)); + + // Update project summary + dispatch( + setProjectSummarySlice({ + totalProjects: projectListRes.totalProjects || 0, + activeProjects: projectListRes.activeProjects || 0, + awayProjects: projectListRes.awayProjects || 0, + totalWorkTime: projectListRes.totalWorkTime || "0h 0m 0s", + totalWorkMs: projectListRes.totalWorkMs || 0, + vaName: projectListRes.vaName || "", + vaId: projectListRes.vaId || "", + }) + ); + + // Update selected project with fresh data from server + const updatedProject = projectListRes.data.find( + (p: Project) => p.projectId === projectId + ); + + if (updatedProject) { + dispatch(setSelectedProjectSlice(updatedProject)); + + // Sync client time with server time + const serverElapsedTime = parseTimeStringToSeconds(updatedProject.totalWorkTime); + + console.log("πŸ”„ Syncing with server time:", { + serverTime: updatedProject.totalWorkTime, + serverSeconds: serverElapsedTime, + clientSeconds: elapsedTime, + difference: serverElapsedTime - elapsedTime + }); + + // Force update the tracking map with server time + dispatch( + updateProjectTracking({ + projectId: updatedProject.projectId, + data: { + elapsedTime: serverElapsedTime, + accumulatedTime: serverElapsedTime, + isTracking: false, + isPaused: false, + startTime: null, + endTime: Date.now(), + pauseStartTime: null, + totalPausedTime: 0 + } + }) + ); + + console.log("βœ… Project synced with server"); + } + } + + + + } catch (error) { + console.error("❌ Error refreshing project list:", error); + } finally { + // Clear loading states + setLoadingStop(false); + setIsStopping(false); + + // βœ… Reset stopping flag for next session + if (setIsStoppingInTracker) { + setIsStoppingInTracker(false); + } + + console.log("=".repeat(70)); + console.log("πŸŽ‰ TRACKING STOPPED SUCCESSFULLY"); + console.log("=".repeat(70)); + } + + // STEP 8: Reset logs for this project + if (logsSent && projectId) { + dispatch(clearProjectLogs({ projectId })); + } + + console.log("πŸŽ‰ TRACKING STOPPED SUCCESSFULLY"); + }, [ + selectedProject, + projectSummary, + dispatch, + loadingStop, + loadingStart, + setIsStoppingInTracker, + finalizeAndSendLogs, + ]); // Update the ref whenever finalizeTracking changes // useEffect(() => { @@ -406,9 +457,9 @@ const DashboardLayout = () => { } // Reset logs for this project if they exist - if (logsByProject[project.projectId]) { - dispatch(clearProjectLogs({ projectId: project.projectId })); - } + if (logsByProject[project.projectId]) { + dispatch(clearProjectLogs({ projectId: project.projectId })); + } // CRITICAL FIX: Get the current accumulated time BEFORE starting timer const currentAccumulatedTime = selectedData?.accumulatedTime || parseTimeStringToSeconds(project.totalWorkTime); diff --git a/src/ui/pages/Vpn/VpnGuard.tsx b/src/ui/pages/Vpn/VpnGuard.tsx index 609dc36..40823d2 100644 --- a/src/ui/pages/Vpn/VpnGuard.tsx +++ b/src/ui/pages/Vpn/VpnGuard.tsx @@ -7,7 +7,7 @@ interface Props { const VpnGuard: React.FC = ({ children }) => { - const VPN_API = "http://10.0.0.2:3000"; + const VPN_API = "http://10.0.0.1:3000"; const DEFAULT_API = "https://quality-friendly-goldfish.ngrok-free.app" const [checking, setChecking] = useState(true); diff --git a/src/ui/store/logsSlice.tsx b/src/ui/store/logsSlice.tsx index 3bec6f9..d8c00fd 100644 --- a/src/ui/store/logsSlice.tsx +++ b/src/ui/store/logsSlice.tsx @@ -2,181 +2,239 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; export interface LogEntry { - id: string; // Unique ID for each log entry - title: string; - appName: string; - url: string | null; - from: string; // UTC ISO string - to: string; // UTC ISO string - totalTime: number; // milliseconds + id: string; // Unique ID for each log entry + title: string; + appName: string; + url: string | null; + from: string; // UTC ISO string with .000 milliseconds + to: string; // UTC ISO string with .000 milliseconds + totalTime: number; // milliseconds } export interface ProjectLogs { - projectId: string; - logId: string | null; - logs: LogEntry[]; // All collected logs during tracking - trackingStartTime: string | null; - lastWindowTitle: string | null; // Track last window title - lastWindowApp: string | null; // Track last window app + projectId: string; + logId: string | null; + logs: LogEntry[]; // All collected logs during tracking + trackingStartTime: string | null; + lastWindowTitle: string | null; // Track last window title + lastWindowApp: string | null; // Track last window app } interface LogsState { - logsByProject: Record; + logsByProject: Record; } const initialState: LogsState = { - logsByProject: {}, + logsByProject: {}, +}; + +// Helper function to get timestamp with .000 milliseconds +const getTimestampWithZeroMs = () => { + const now = new Date(); + // Format: YYYY-MM-DDTHH:mm:ss.000Z + const isoString = now.toISOString(); + // Replace milliseconds with .000 + return isoString.replace(/\.\d{3}Z$/, '.000Z'); +}; + +// Helper function to format any date string to have .000 milliseconds +const formatToZeroMs = (dateString: string): string => { + if (dateString.endsWith('.000Z')) { + return dateString; // Already formatted + } + const date = new Date(dateString); + const isoString = date.toISOString(); + return isoString.replace(/\.\d{3}Z$/, '.000Z'); }; const logsSlice = createSlice({ - name: "logs", - initialState, - reducers: { - // Initialize logs for a project - initializeProjectLogs: ( - state, - action: PayloadAction<{ - projectId: string; - logId: string; - }> - ) => { - const { projectId, logId } = action.payload; - const nowUTC = new Date().toISOString(); + name: "logs", + initialState, + reducers: { + // Initialize logs for a project + initializeProjectLogs: ( + state, + action: PayloadAction<{ + projectId: string; + logId: string; + }> + ) => { + const { projectId, logId } = action.payload; + const nowUTC = getTimestampWithZeroMs(); - // Always create fresh logs for new tracking session - state.logsByProject[projectId] = { - projectId, - logId, - logs: [], // Start with empty logs - trackingStartTime: nowUTC, - lastWindowTitle: null, - lastWindowApp: null, - }; + // Always create fresh logs for new tracking session + state.logsByProject[projectId] = { + projectId, + logId, + logs: [], // Start with empty logs + trackingStartTime: nowUTC, + lastWindowTitle: null, + lastWindowApp: null, + }; - console.log(`πŸ“ Initialized logs for project ${projectId} with logId: ${logId}`); + console.log(`πŸ“ Initialized logs for project ${projectId} with logId: ${logId}`); + }, + + // Add or update window log + updateWindowLog: ( + state, + action: PayloadAction<{ + projectId: string; + title: string; + appName: string; + url?: string | null; + }> + ) => { + const { projectId, title, appName, url = null } = action.payload; + const projectLogs = state.logsByProject[projectId]; + + if (!projectLogs) { + console.warn(`Cannot update log: Project ${projectId} not initialized`); + return; + } + + const nowUTC = getTimestampWithZeroMs(); + const isSameWindow = + projectLogs.lastWindowTitle === title && + projectLogs.lastWindowApp === appName; + + // If same window as before, update the last log's end time + if (isSameWindow && projectLogs.logs.length > 0) { + const lastLog = projectLogs.logs[projectLogs.logs.length - 1]; + // Format the existing from time to ensure it has .000 milliseconds + lastLog.from = formatToZeroMs(lastLog.from); + lastLog.to = nowUTC; + + // Calculate total time in milliseconds + const startTime = new Date(lastLog.from).getTime(); + const endTime = new Date(lastLog.to).getTime(); + lastLog.totalTime = endTime - startTime; + + // console.log(`⏱️ Updated existing log: ${title} (${lastLog.totalTime}ms)`); + } + // If new window or first window + else { + // If there was a previous window, ensure it's properly ended + if (projectLogs.lastWindowTitle && projectLogs.logs.length > 0) { + const lastLog = projectLogs.logs[projectLogs.logs.length - 1]; + // Format the existing from time to ensure it has .000 milliseconds + lastLog.from = formatToZeroMs(lastLog.from); + lastLog.to = nowUTC; + + // Calculate total time in milliseconds + const startTime = new Date(lastLog.from).getTime(); + const endTime = new Date(lastLog.to).getTime(); + lastLog.totalTime = endTime - startTime; + + console.log( + `βœ… Finalized previous window: ${lastLog.title} (${lastLog.totalTime}ms)` + ); + } + + // Create new log entry with .000 milliseconds + const newLog: LogEntry = { + id: `log_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, + title, + appName, + url, + from: nowUTC, + to: nowUTC, // Will be updated continuously + totalTime: 0, // Starts at 0 (in milliseconds) + }; + + projectLogs.logs.push(newLog); + console.log(`πŸ“ Started new window: ${title} on ${appName}`); + } + + // Update last window tracking + projectLogs.lastWindowTitle = title; + projectLogs.lastWindowApp = appName; + }, + + // Clear project logs + clearProjectLogs: ( + state, + action: PayloadAction<{ + projectId: string; + }> + ) => { + const { projectId } = action.payload; + delete state.logsByProject[projectId]; + console.log(`🧹 Cleared logs for project ${projectId}`); + }, + + // Clear all logs + clearAllLogs: (state) => { + state.logsByProject = {}; + console.log("🧹 Cleared all logs"); + }, + + // Debug: Get logs state + debugLogsState: ( + state, + action: PayloadAction<{ + projectId: string; + }> + ) => { + const { projectId } = action.payload; + const projectLogs = state.logsByProject[projectId]; + + if (!projectLogs) { + console.log(`πŸ“‹ No logs found for project ${projectId}`); + return; + } + + console.log(`πŸ“‹ Logs State for ${projectId}:`, { + totalLogs: projectLogs.logs.length, + lastWindow: projectLogs.lastWindowTitle, + logs: projectLogs.logs.map((log) => ({ + title: log.title.substring(0, 40) + (log.title.length > 40 ? "..." : ""), + appName: log.appName, + from: log.from, + to: log.to, + durationMs: log.totalTime, + durationSec: Math.round(log.totalTime / 1000), + })), + }); + }, + + // Optional: Add a reducer to fix all timestamps in existing logs + fixAllTimestamps: ( + state, + action: PayloadAction<{ + projectId: string; + }> + ) => { + const { projectId } = action.payload; + const projectLogs = state.logsByProject[projectId]; + + if (!projectLogs) { + console.warn(`Cannot fix timestamps: Project ${projectId} not found`); + return; + } + + // Fix all timestamps in existing logs + projectLogs.logs.forEach(log => { + log.from = formatToZeroMs(log.from); + log.to = formatToZeroMs(log.to); + // Recalculate totalTime based on formatted timestamps + const startTime = new Date(log.from).getTime(); + const endTime = new Date(log.to).getTime(); + log.totalTime = endTime - startTime; + }); + + console.log(`πŸ”§ Fixed timestamps for project ${projectId}`); + }, }, - - // Add or update window log - updateWindowLog: ( - state, - action: PayloadAction<{ - projectId: string; - title: string; - appName: string; - url?: string | null; - }> - ) => { - const { projectId, title, appName, url = null } = action.payload; - const projectLogs = state.logsByProject[projectId]; - - if (!projectLogs) { - console.warn(`Cannot update log: Project ${projectId} not initialized`); - return; - } - - const nowUTC = new Date().toISOString(); - const isSameWindow = - projectLogs.lastWindowTitle === title && - projectLogs.lastWindowApp === appName; - - // If same window as before, update the last log's end time - if (isSameWindow && projectLogs.logs.length > 0) { - const lastLog = projectLogs.logs[projectLogs.logs.length - 1]; - const startTime = new Date(lastLog.from); - const endTime = new Date(nowUTC); - lastLog.to = nowUTC; - lastLog.totalTime = endTime.getTime() - startTime.getTime(); - // console.log(`⏱️ Updated existing log: ${title} (${lastLog.totalTime}ms)`); - } - // If new window or first window - else { - // If there was a previous window, ensure it's properly ended - if (projectLogs.lastWindowTitle && projectLogs.logs.length > 0) { - const lastLog = projectLogs.logs[projectLogs.logs.length - 1]; - const endTime = new Date(nowUTC); - const startTime = new Date(lastLog.from); - lastLog.to = nowUTC; - lastLog.totalTime = endTime.getTime() - startTime.getTime(); - console.log( - `βœ… Finalized previous window: ${lastLog.title} (${lastLog.totalTime}ms)` - ); - } - - // Create new log entry - const newLog: LogEntry = { - id: `log_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, - title, - appName, - url, - from: nowUTC, - to: nowUTC, // Will be updated continuously - totalTime: 0, // Starts at 0 - }; - - projectLogs.logs.push(newLog); - console.log(`πŸ“ Started new window: ${title} on ${appName}`); - } - - // Update last window tracking - projectLogs.lastWindowTitle = title; - projectLogs.lastWindowApp = appName; - }, - - // Clear project logs - clearProjectLogs: ( - state, - action: PayloadAction<{ - projectId: string; - }> - ) => { - const { projectId } = action.payload; - delete state.logsByProject[projectId]; - console.log(`🧹 Cleared logs for project ${projectId}`); - }, - - // Clear all logs - clearAllLogs: (state) => { - state.logsByProject = {}; - console.log("🧹 Cleared all logs"); - }, - - // Debug: Get logs state - debugLogsState: ( - state, - action: PayloadAction<{ - projectId: string; - }> - ) => { - const { projectId } = action.payload; - const projectLogs = state.logsByProject[projectId]; - - if (!projectLogs) { - console.log(`πŸ“‹ No logs found for project ${projectId}`); - return; - } - - console.log(`πŸ“‹ Logs State for ${projectId}:`, { - totalLogs: projectLogs.logs.length, - lastWindow: projectLogs.lastWindowTitle, - logs: projectLogs.logs.map((log) => ({ - title: - log.title.substring(0, 40) + (log.title.length > 40 ? "..." : ""), - appName: log.appName, - from: new Date(log.from).toLocaleTimeString(), - to: new Date(log.to).toLocaleTimeString(), - duration: log.totalTime, - })), - }); - }, - }, }); export const { - initializeProjectLogs, - updateWindowLog, - clearProjectLogs, - clearAllLogs, - debugLogsState, + initializeProjectLogs, + updateWindowLog, + clearProjectLogs, + clearAllLogs, + debugLogsState, + fixAllTimestamps, } = logsSlice.actions; export default logsSlice.reducer; \ No newline at end of file diff --git a/types.d.ts b/types.d.ts index 9464e15..bf8f151 100644 --- a/types.d.ts +++ b/types.d.ts @@ -185,6 +185,7 @@ interface Window { }) => Promise; stopProjectTracking: (data: { vaId: string; + logId: string, projectId: string; type: string; time: string From b4403c2cd5b9faab06f8247eccd55de26fadd644 Mon Sep 17 00:00:00 2001 From: JaiPandey425 Date: Thu, 18 Dec 2025 15:32:19 +0530 Subject: [PATCH 11/14] fixed issue if no project assign --- resources/wireguard/windows/wg0.conf | 7 ++-- src/electron/api/axiosClient.ts | 49 ++++++++++++++++++-------- src/electron/api/baseURL.ts | 3 ++ src/ui/hooks/useActiveAppTracker.tsx | 2 +- src/ui/pages/Dashboard.tsx | 43 +++++++++++++--------- src/ui/pages/PaySlips/PaysSlips.tsx | 2 ++ src/ui/pages/Timesheets/Timesheets.tsx | 10 +++--- src/ui/pages/Vpn/VpnGuard.tsx | 3 +- 8 files changed, 77 insertions(+), 42 deletions(-) diff --git a/resources/wireguard/windows/wg0.conf b/resources/wireguard/windows/wg0.conf index c5a6248..6e24434 100644 --- a/resources/wireguard/windows/wg0.conf +++ b/resources/wireguard/windows/wg0.conf @@ -1,12 +1,11 @@ [Interface] -PrivateKey = EA2j6mOQB17w497cZS4B91m/xDzEcGz9lU3jKtc1wU8= -Address = 10.0.0.27/32 +PrivateKey = yHrWqSi5ZV4YV1cVVPBt2NbmTHd/mDWtZGVWGQe5I3s= +Address = 10.0.0.91/32 DNS = 1.1.1.1 [Peer] PublicKey = jG+7H5thEiNjxY1XqI1R9oyMAMTIegLwE3tZkS1Rj2U= -AllowedIPs = 10.0.0.0/24 Endpoint = 35.95.209.161:51820 AllowedIPs = 10.0.0.0/24 -PersistentKeepalive = 25 \ No newline at end of file +PersistentKeepalive = 25 diff --git a/src/electron/api/axiosClient.ts b/src/electron/api/axiosClient.ts index 3cd6426..f2daad5 100644 --- a/src/electron/api/axiosClient.ts +++ b/src/electron/api/axiosClient.ts @@ -1,5 +1,5 @@ // electron/api/axiosClient.ts -import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'; +import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios'; import { getToken } from '../utils/tokenHelper.js'; import chalk from "chalk"; import { sendTokenExpired } from '../ipc/ipcHandler.js'; @@ -8,40 +8,61 @@ import { getBaseURL } from './baseURL.js'; const TOKEN_KEY = 'ACCESS_TOKEN'; -const Base_URL = await getBaseURL() +// const Base_URL = await getBaseURL() const httpsAgent = new https.Agent({ rejectUnauthorized: false }); -console.log('Base_URL$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$' ,Base_URL) +// console.log('Base_URL$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$' ,Base_URL) const api: AxiosInstance = axios.create({ // baseURL: 'https://portal-sf.docva.com/api/v1/va', // πŸ” your backend - baseURL: `${Base_URL}/api/v1/va`, + // baseURL: `${Base_URL}/api/v1/va`, httpsAgent // timeout: 10000, }); + +export function syncAxiosBaseURL() { + const baseURL = getBaseURL(); + api.defaults.baseURL = `${baseURL}/api/v1/va`; + + console.log('βœ… Axios baseURL synced β†’', api.defaults.baseURL); +} + // console.log("Creating Axios Instance", axios.VERSION); // console.log("Axios .get reference:", api.get); // console.log("Axios .request reference:", api.request); // --- Request Interceptor --- +// api.interceptors.request.use( +// async (config: AxiosRequestConfig): Promise => { +// const token = await getToken(TOKEN_KEY); +// config.headers = config.headers || {} + +// if (token) { +// config.headers['token'] = token; +// config.headers['ngrok-skip-browser-warning'] = 'true'; +// } + +// return config; +// }, +// (error) => Promise.reject(error) +// ); + api.interceptors.request.use( - async (config: AxiosRequestConfig): Promise => { - const token = await getToken(TOKEN_KEY); - config.headers = config.headers || {} + async (config: InternalAxiosRequestConfig) => { + const token = await getToken(TOKEN_KEY); - if (token) { - config.headers['token'] = token; - config.headers['ngrok-skip-browser-warning'] = 'true'; - } + if (token) { + config.headers.token = token; + config.headers['ngrok-skip-browser-warning'] = 'true'; + } - return config; - }, - (error) => Promise.reject(error) + return config; + } ); // --- Response Interceptor --- diff --git a/src/electron/api/baseURL.ts b/src/electron/api/baseURL.ts index 2a33d5a..93c83c0 100644 --- a/src/electron/api/baseURL.ts +++ b/src/electron/api/baseURL.ts @@ -1,7 +1,10 @@ +import { syncAxiosBaseURL } from "./axiosClient.js"; + // let CURRENT_BASE_URL = "https://portal-sf.docva.com/api/v1/va"; let CURRENT_BASE_URL = "https://portal-sf.docva.com"; export const getBaseURL = () => CURRENT_BASE_URL; export const setBaseURL = (url: string) => { CURRENT_BASE_URL = url; + syncAxiosBaseURL(); }; diff --git a/src/ui/hooks/useActiveAppTracker.tsx b/src/ui/hooks/useActiveAppTracker.tsx index 2993c5e..d66fb60 100644 --- a/src/ui/hooks/useActiveAppTracker.tsx +++ b/src/ui/hooks/useActiveAppTracker.tsx @@ -132,7 +132,7 @@ const finalizeAndSendLogs = useCallback( // } : null, // }); - let res = window.electron.trackingTabsOpeningClosing(payload); + let res = await window.electron.trackingTabsOpeningClosing(payload); console.log("Tracking response:", res); dispatch(clearProjectLogs({ projectId })); diff --git a/src/ui/pages/Dashboard.tsx b/src/ui/pages/Dashboard.tsx index 46c89f5..9216697 100644 --- a/src/ui/pages/Dashboard.tsx +++ b/src/ui/pages/Dashboard.tsx @@ -152,6 +152,14 @@ const Dashboard: React.FC = () => { }) ); + // FIX: Check if we have any projects + if (res.data.length === 0) { + // No projects available, clear selected project + dispatch(setSelectedProjectSlice(null)); + // fetchProfile(); + return; + } + // Check if we already have a selected project that's being tracked const currentlySelected = selectedProject; const currentlyTracking = isTracking; @@ -208,9 +216,13 @@ const Dashboard: React.FC = () => { fetchProfile(); } else { console.warn("⚠️ Unexpected response:", res); + // FIX: Also clear selected project on unexpected response + dispatch(setSelectedProjectSlice(null)); } } catch (err) { console.error("❌ Error fetching projects:", err); + // FIX: Clear selected project on error + dispatch(setSelectedProjectSlice(null)); } finally { setIsRefreshing(false); } @@ -218,6 +230,7 @@ const Dashboard: React.FC = () => { useEffect(() => { fetchProjects(); + fetchProfile() }, []); const handleRefresh = async () => { @@ -285,6 +298,7 @@ const Dashboard: React.FC = () => {

Dashboard

{/* Tracking Section */} + {/* Tracking Section */}
@@ -305,11 +319,6 @@ const Dashboard: React.FC = () => {

{formatTime(displayTime)}

- - {/*

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

*/}
Project:{" "} @@ -356,7 +365,9 @@ const Dashboard: React.FC = () => { ) : (

- Select a project below to start tracking. + {projectsData.length === 0 + ? "No projects available." + : "Select a project below to start tracking."}

)}
@@ -368,16 +379,16 @@ const Dashboard: React.FC = () => { {/* πŸ”Ή Sticky Header */}

Projects

- {!selectedData?.isTracking && - } + {!selectedData?.isTracking && + }
{/* πŸ”Ή Table or Empty State or Skeleton */} diff --git a/src/ui/pages/PaySlips/PaysSlips.tsx b/src/ui/pages/PaySlips/PaysSlips.tsx index 5c9b931..0e1a51e 100644 --- a/src/ui/pages/PaySlips/PaysSlips.tsx +++ b/src/ui/pages/PaySlips/PaysSlips.tsx @@ -9,6 +9,8 @@ const PaysSlips = () => { const [error, setError] = useState(null); const [rows, setRows] = useState([]); + + useEffect(() => { if (!projectSummary?.vaId) return; diff --git a/src/ui/pages/Timesheets/Timesheets.tsx b/src/ui/pages/Timesheets/Timesheets.tsx index 8ca6afc..49cdb60 100644 --- a/src/ui/pages/Timesheets/Timesheets.tsx +++ b/src/ui/pages/Timesheets/Timesheets.tsx @@ -64,13 +64,11 @@ 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 { selectedProject } = useSelector((state: any) => state.projects); + const selectedProjectId = selectedProject?.projectId ?? null; const activeProjects = projectsData.filter(p => p.status === "ACTIVE"); - console.log("selectedProject", selectedProject.projectId) - useEffect(() => { const interval = setInterval(() => { @@ -225,13 +223,13 @@ const Timesheets: React.FC = () => {
{formatWorkTime(entry.totalWorkTime)} {/* Disconnect VPN (Disabled when NOT connected) */} - + */}

From 85a178e8251407cf1932566c97b6183aba16812a Mon Sep 17 00:00:00 2001 From: BhagwanSingh Date: Thu, 18 Dec 2025 15:57:34 +0530 Subject: [PATCH 13/14] Added Logout Option --- resources/wireguard/windows/wg0.conf | 4 ++-- src/electron/api/axiosClient.ts | 2 +- src/electron/api/baseURL.ts | 4 ++-- src/electron/main.ts | 9 ++++--- src/ui/layout/DashboardLayout.tsx | 2 +- src/ui/main.tsx | 35 ++++++++++++++++++++-------- src/ui/pages/Vpn/VpnGuard.tsx | 2 +- 7 files changed, 38 insertions(+), 20 deletions(-) diff --git a/resources/wireguard/windows/wg0.conf b/resources/wireguard/windows/wg0.conf index f5bffdd..e47a3c7 100644 --- a/resources/wireguard/windows/wg0.conf +++ b/resources/wireguard/windows/wg0.conf @@ -1,7 +1,7 @@ [Interface] -PrivateKey = GKXyqCZfS9r2yWzqHJ43L24dnW13VJNilER9jwkyREE= -Address = 10.0.0.95/32 +PrivateKey = OPasvZsdAdU46BJLXOLMqy4BgZq/UvKtjTRpTTVSGX8= +Address = 10.0.0.96/32 DNS = 1.1.1.1 [Peer] diff --git a/src/electron/api/axiosClient.ts b/src/electron/api/axiosClient.ts index bb23df0..3cd6426 100644 --- a/src/electron/api/axiosClient.ts +++ b/src/electron/api/axiosClient.ts @@ -18,7 +18,7 @@ const httpsAgent = new https.Agent({ console.log('Base_URL$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$' ,Base_URL) const api: AxiosInstance = axios.create({ - // baseURL: 'https://quality-friendly-goldfish.ngrok-free.app/api/v1/va', // πŸ” your backend + // baseURL: 'https://portal-sf.docva.com/api/v1/va', // πŸ” your backend baseURL: `${Base_URL}/api/v1/va`, httpsAgent // timeout: 10000, diff --git a/src/electron/api/baseURL.ts b/src/electron/api/baseURL.ts index 67478cd..2a33d5a 100644 --- a/src/electron/api/baseURL.ts +++ b/src/electron/api/baseURL.ts @@ -1,5 +1,5 @@ -// let CURRENT_BASE_URL = "https://quality-friendly-goldfish.ngrok-free.app/api/v1/va"; -let CURRENT_BASE_URL = "https://quality-friendly-goldfish.ngrok-free.app"; +// let CURRENT_BASE_URL = "https://portal-sf.docva.com/api/v1/va"; +let CURRENT_BASE_URL = "https://portal-sf.docva.com"; export const getBaseURL = () => CURRENT_BASE_URL; export const setBaseURL = (url: string) => { diff --git a/src/electron/main.ts b/src/electron/main.ts index 3fa2016..46bd7aa 100644 --- a/src/electron/main.ts +++ b/src/electron/main.ts @@ -178,6 +178,12 @@ app.on("ready", async () => { // HANDLE CLOSE EVENTS // ------------------------------------ // handleCloseEvents(win); + mainWindow.on("close", () => { + if(mainWindow !== null) { + mainWindow.webContents.executeJavaScript(`window.__allowClose__ = true`); + } + }); + }); @@ -197,9 +203,6 @@ app.on('browser-window-focus', () => { // globalShortcut.unregisterAll(); // }); -app.on("window-all-closed", () => { - app.quit(); -}); // --------------------------- diff --git a/src/ui/layout/DashboardLayout.tsx b/src/ui/layout/DashboardLayout.tsx index d066022..2c45e1e 100644 --- a/src/ui/layout/DashboardLayout.tsx +++ b/src/ui/layout/DashboardLayout.tsx @@ -592,7 +592,7 @@ const DashboardLayout = () => { if (!selectedProject?.projectId) return console.log(selectedProject?.projectId, "SelectedProject ID") - socketRef.current = io("https://quality-friendly-goldfish.ngrok-free.app", { + socketRef.current = io("https://portal-sf.docva.com", { transports: ["websocket"], secure: true, rejectUnauthorized: false, diff --git a/src/ui/main.tsx b/src/ui/main.tsx index 5f1d991..9d1b9fc 100644 --- a/src/ui/main.tsx +++ b/src/ui/main.tsx @@ -6,19 +6,34 @@ import { Provider } from "react-redux"; import { store } from "./store"; import { ToastProvider } from './helper/ToastContext'; -// renderer only -// window.addEventListener("online", (e) => { -// console.log("Network online (ignored)"); -// e.preventDefault(); -// }); +declare global { + interface Window { + __allowClose__: boolean; + } +} -// window.addEventListener("offline", (e) => { -// console.log("Network offline (ignored)"); -// e.preventDefault(); -// }); + +// renderer only +window.addEventListener("online", (e) => { + console.log("Network online (ignored)"); + e.preventDefault(); +}); + +window.addEventListener("offline", (e) => { + console.log("Network offline (ignored)"); + e.preventDefault(); +}); // Extra safety -window.onbeforeunload = () => false; +window.__allowClose__ = false; + +window.addEventListener("beforeunload", (e) => { + if (!window.__allowClose__) { + e.returnValue = false; + } +}); + + ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( diff --git a/src/ui/pages/Vpn/VpnGuard.tsx b/src/ui/pages/Vpn/VpnGuard.tsx index 9f01e0e..060ac20 100644 --- a/src/ui/pages/Vpn/VpnGuard.tsx +++ b/src/ui/pages/Vpn/VpnGuard.tsx @@ -8,7 +8,7 @@ interface Props { const VpnGuard: React.FC = ({ children }) => { const VPN_API = "http://10.0.0.1:3000"; - const DEFAULT_API = "https://quality-friendly-goldfish.ngrok-free.app" + const DEFAULT_API = "https://portal-sf.docva.com" const [checking, setChecking] = useState(true); const [vpnConnected, setVpnConnected] = useState(true); From 9eb1a3ff1ed36795e2e114bad4575ff98c9c9512 Mon Sep 17 00:00:00 2001 From: BhagwanSingh Date: Thu, 18 Dec 2025 16:14:49 +0530 Subject: [PATCH 14/14] A --- .gitignore | 1 + resources/wireguard/windows/wg0.conf | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index aab0b66..c5f36f5 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,4 @@ nextSteps.txt /playwright-report/ /blob-report/ /playwright/.cache/ +resources \ No newline at end of file diff --git a/resources/wireguard/windows/wg0.conf b/resources/wireguard/windows/wg0.conf index 6210f37..88400b7 100644 --- a/resources/wireguard/windows/wg0.conf +++ b/resources/wireguard/windows/wg0.conf @@ -1,7 +1,7 @@ [Interface] -PrivateKey = OPasvZsdAdU46BJLXOLMqy4BgZq/UvKtjTRpTTVSGX8= -Address = 10.0.0.96/32 +PrivateKey = QBPeUFxS5EVEZT2QRhMgSW4gUXG2WoUTlUFg/p9ZEEk= +Address = 10.0.0.97/32 DNS = 1.1.1.1 [Peer] @@ -9,4 +9,3 @@ PublicKey = jG+7H5thEiNjxY1XqI1R9oyMAMTIegLwE3tZkS1Rj2U= Endpoint = 35.95.209.161:51820 AllowedIPs = 10.0.0.0/24 PersistentKeepalive = 25 -