Merge branch 'window-build' of http://git.hicglobalsolutions.in/BhawanSingh/va-tracker - Window-Build Merge 10 Dec
This commit is contained in:
commit
9172534f31
|
|
@ -1,10 +1,10 @@
|
||||||
[Interface]
|
[Interface]
|
||||||
PrivateKey = CEzCd25ufxbOjvRhMqLfkcdP/OziG5i6OkXCfMZrI1I=
|
PrivateKey = CEzCd25ufxbOjvRhMqLfkcdP/OziG5i6OkXCfMZrI1I=
|
||||||
Address = 10.0.0.2/24
|
Address = 10.0.0.2/32
|
||||||
DNS = 1.1.1.1
|
DNS = 1.1.1.1
|
||||||
|
|
||||||
[Peer]
|
[Peer]
|
||||||
PublicKey = jG+7H5thEiNjxY1XqI1R9oyMAMTIegLwE3tZkS1Rj2U=
|
PublicKey = jG+7H5thEiNjxY1XqI1R9oyMAMTIegLwE3tZkS1Rj2U=
|
||||||
Endpoint = 35.95.209.161:51820
|
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
|
PersistentKeepalive = 25
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
let CURRENT_BASE_URL = "https://quality-friendly-goldfish.ngrok-free.app/api/v1/va";
|
||||||
|
|
||||||
|
export const getBaseURL = () => CURRENT_BASE_URL;
|
||||||
|
export const setBaseURL = (url: string) => {
|
||||||
|
CURRENT_BASE_URL = url;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
|
||||||
|
import axios from "axios";
|
||||||
|
import https from "https";
|
||||||
|
import { getBaseURL } from "./baseURL.js";
|
||||||
|
|
||||||
|
const httpsAgent = new https.Agent({ rejectUnauthorized: false });
|
||||||
|
|
||||||
|
export const checkRegion = async () => {
|
||||||
|
const baseURL = await getBaseURL();
|
||||||
|
|
||||||
|
console.log("checkRegion@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" ,baseURL)
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
const res = await axios.get(`${baseURL}/health`, {
|
||||||
|
headers: { "ngrok-skip-browser-warning": "true" },
|
||||||
|
httpsAgent
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(res , "checkRegion")
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
status: res.status,
|
||||||
|
data: res.data
|
||||||
|
};
|
||||||
|
|
||||||
|
} catch (error: any) {
|
||||||
|
console.log(error , "Line 29")
|
||||||
|
const status = error?.response?.status || 500;
|
||||||
|
|
||||||
|
console.log("Region Check Error:", status);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
status,
|
||||||
|
message: "Region check failed"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -3,7 +3,9 @@ import { getToken, saveToken, clearToken } from "../utils/tokenHelper.js";
|
||||||
import { getActiveApp, handleLogin, takeScreenshot, startWindowPolling, getRunningAppsList, captureSpecificApp } from "../utils/utilityFunctions.js";
|
import { getActiveApp, handleLogin, takeScreenshot, startWindowPolling, getRunningAppsList, captureSpecificApp } from "../utils/utilityFunctions.js";
|
||||||
import api from "../api/axiosClient.js";
|
import api from "../api/axiosClient.js";
|
||||||
import { getMainWindow } from '../main.js';
|
import { getMainWindow } from '../main.js';
|
||||||
|
import { checkRegion } from "../api/regionCheck.js";
|
||||||
import { startVPN, stopVPN } from "../vpn.js";
|
import { startVPN, stopVPN } from "../vpn.js";
|
||||||
|
import { getBaseURL, setBaseURL } from "../api/baseURL.js";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -11,6 +13,20 @@ import { startVPN, stopVPN } from "../vpn.js";
|
||||||
export const registerIPCHandlers = (mainWindow: BrowserWindow) => {
|
export const registerIPCHandlers = (mainWindow: BrowserWindow) => {
|
||||||
console.log("⚙️ Registering IPC Handlers...");
|
console.log("⚙️ Registering IPC Handlers...");
|
||||||
|
|
||||||
|
|
||||||
|
ipcMain.handle("GET_BASE_URL", () => {
|
||||||
|
return getBaseURL();
|
||||||
|
});
|
||||||
|
|
||||||
|
// UPDATE Base URL
|
||||||
|
ipcMain.handle("UPDATE_BASE_URL", (event, url: string) => {
|
||||||
|
setBaseURL(url);
|
||||||
|
return {
|
||||||
|
updated: true,
|
||||||
|
baseURL: url
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
ipcMain.handle("get-token", async (_, key: "ACCESS_TOKEN" | "REFRESH_TOKEN") => {
|
ipcMain.handle("get-token", async (_, key: "ACCESS_TOKEN" | "REFRESH_TOKEN") => {
|
||||||
return await getToken(key);
|
return await getToken(key);
|
||||||
});
|
});
|
||||||
|
|
@ -535,6 +551,11 @@ ipcMain.handle("update-payment", async (event, data: Record<string, any>) => {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ipcMain.handle("check-region", async () => {
|
||||||
|
return await checkRegion();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const sendTokenExpired = () => {
|
export const sendTokenExpired = () => {
|
||||||
console.log("⚠️ Sending token-expired event to renderer");
|
console.log("⚠️ Sending token-expired event to renderer");
|
||||||
|
|
|
||||||
|
|
@ -65,9 +65,14 @@ contextBridge.exposeInMainWorld('electron', {
|
||||||
getLeaveList: () => ipcRenderer.invoke("get-leave-list"),
|
getLeaveList: () => ipcRenderer.invoke("get-leave-list"),
|
||||||
getAdjustmentsList:(vaId: string) => ipcRenderer.invoke("get-adjustments-list",vaId),
|
getAdjustmentsList:(vaId: string) => ipcRenderer.invoke("get-adjustments-list",vaId),
|
||||||
|
|
||||||
|
checkRegion: () => ipcRenderer.invoke("check-region"),
|
||||||
|
|
||||||
connectVPN: () => ipcRenderer.invoke("vpn-connect"),
|
connectVPN: () => ipcRenderer.invoke("vpn-connect"),
|
||||||
disconnectVPN: () => ipcRenderer.invoke("vpn-disconnect"),
|
disconnectVPN: () => ipcRenderer.invoke("vpn-disconnect"),
|
||||||
|
|
||||||
|
getBaseURL: () => ipcRenderer.invoke("GET_BASE_URL"),
|
||||||
|
updateBaseURL: (url: string) => ipcRenderer.invoke("UPDATE_BASE_URL", url),
|
||||||
|
|
||||||
} satisfies Window['electron']);
|
} satisfies Window['electron']);
|
||||||
|
|
||||||
// function ipcInvoke<Key extends keyof EventPayloadMapping>(
|
// function ipcInvoke<Key extends keyof EventPayloadMapping>(
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { saveToken } from "./tokenHelper.js";
|
||||||
import { activeWindow } from "get-windows";
|
import { activeWindow } from "get-windows";
|
||||||
import { exec } from "child_process";
|
import { exec } from "child_process";
|
||||||
import { getUIPath } from "../pathResolver.js";
|
import { getUIPath } from "../pathResolver.js";
|
||||||
|
import { getBaseURL } from "../api/baseURL.js";
|
||||||
|
|
||||||
const httpsAgent = new https.Agent({
|
const httpsAgent = new https.Agent({
|
||||||
rejectUnauthorized: false
|
rejectUnauthorized: false
|
||||||
|
|
@ -61,14 +62,16 @@ export const handleLogin = async (credentials: { email: string, password: string
|
||||||
// Call your login API
|
// Call your login API
|
||||||
console.log("🟡 login Request received", credentials);
|
console.log("🟡 login Request received", credentials);
|
||||||
|
|
||||||
|
let baseURL = getBaseURL()
|
||||||
|
|
||||||
console.log("app.getAppPath()", app.getAppPath());
|
console.log("app.getAppPath()", app.getAppPath());
|
||||||
console.log("UIPATH", getUIPath());
|
console.log("UIPATH", getUIPath());
|
||||||
console.log("Doom 1");
|
console.log("Doom 1" , `${baseURL}/api/v1/va/login`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log("➡️ Calling API...");
|
console.log("➡️ Calling API...");
|
||||||
const response = await axios.post(
|
const response = await axios.post(
|
||||||
'https://quality-friendly-goldfish.ngrok-free.app/api/v1/va/login',
|
`https://quality-friendly-goldfish.ngrok-free.app/api/v1/va/login`,
|
||||||
{ userDetails: { ...credentials } },
|
{ userDetails: { ...credentials } },
|
||||||
{ headers: { "ngrok-skip-browser-warning": 69420 }, httpsAgent }
|
{ headers: { "ngrok-skip-browser-warning": 69420 }, httpsAgent }
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,7 @@ import Vpn from "./pages/Vpn/Vpn";
|
||||||
import Schedule from "./pages/Schedule/Schedule";
|
import Schedule from "./pages/Schedule/Schedule";
|
||||||
import PaysSlips from "./pages/PaySlips/PaysSlips";
|
import PaysSlips from "./pages/PaySlips/PaysSlips";
|
||||||
import ApplyLeaveAndAdjustmentsTime from "./pages/LeaveAndAdjustments/ApplyLeaveAndAdjustmentsTime";
|
import ApplyLeaveAndAdjustmentsTime from "./pages/LeaveAndAdjustments/ApplyLeaveAndAdjustmentsTime";
|
||||||
|
import VpnGuard from "./pages/Vpn/VpnGuard";
|
||||||
|
|
||||||
|
|
||||||
const App: React.FC = () => {
|
const App: React.FC = () => {
|
||||||
|
|
@ -122,7 +123,9 @@ const App: React.FC = () => {
|
||||||
path="/login"
|
path="/login"
|
||||||
element={
|
element={
|
||||||
<PublicRoute user={user}>
|
<PublicRoute user={user}>
|
||||||
|
{/* <VpnGuard> */}
|
||||||
<Login />
|
<Login />
|
||||||
|
{/* </VpnGuard> */}
|
||||||
</PublicRoute>
|
</PublicRoute>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
@ -132,20 +135,22 @@ const App: React.FC = () => {
|
||||||
path="/dashboard"
|
path="/dashboard"
|
||||||
element={
|
element={
|
||||||
<ProtectedRoute user={user}>
|
<ProtectedRoute user={user}>
|
||||||
|
{/* <VpnGuard> */}
|
||||||
<DashboardLayout />
|
<DashboardLayout />
|
||||||
|
{/* </VpnGuard> */}
|
||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Route index element={<Dashboard />} />
|
<Route index element={<Dashboard />} />
|
||||||
<Route path="projects" element={<Projects />} />
|
<Route path="projects" element={<Projects />} />
|
||||||
<Route path="leave-adjustments" element={<LeaveAndAdjustments />} />
|
<Route path="leave-adjustments" element={<LeaveAndAdjustments />} />
|
||||||
<Route path="leave-adjustments/apply" element={<ApplyLeaveAndAdjustmentsTime/>} />
|
<Route path="leave-adjustments/apply" element={<ApplyLeaveAndAdjustmentsTime />} />
|
||||||
<Route path="timesheet" element={<Timesheets />} />
|
<Route path="timesheet" element={<Timesheets />} />
|
||||||
<Route path="settings" element={<Settings />} />
|
<Route path="settings" element={<Settings />} />
|
||||||
<Route path="vpn" element={<Vpn />} />
|
<Route path="vpn" element={<Vpn />} />
|
||||||
<Route path="help-support" element={<HelpSupport />} />
|
<Route path="help-support" element={<HelpSupport />} />
|
||||||
<Route path="schedule" element={<Schedule/>} />
|
<Route path="schedule" element={<Schedule />} />
|
||||||
<Route path="pays-slips" element={<PaysSlips/>} />
|
<Route path="pays-slips" element={<PaysSlips />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
{/* Default Redirect */}
|
{/* Default Redirect */}
|
||||||
|
|
|
||||||
|
|
@ -167,6 +167,8 @@ const DashboardLayout = () => {
|
||||||
`Timer will continue running. Please stop manually when ready.`
|
`Timer will continue running. Please stop manually when ready.`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
console.log("✅ Alert shown, timer continues running");
|
console.log("✅ Alert shown, timer continues running");
|
||||||
}, [selectedProject]);
|
}, [selectedProject]);
|
||||||
|
|
||||||
|
|
@ -724,15 +726,26 @@ const DashboardLayout = () => {
|
||||||
// return `${hrs}h ${mins}m `;
|
// return `${hrs}h ${mins}m `;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const [checkIp, setCheckIp] = useState(false)
|
||||||
|
useEffect(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
fetch('https://api.ipify.org/?format=json').then((res) => console.log("IPPPPPPPPPPPPPP", res))
|
||||||
|
}, 5000)
|
||||||
|
}, [checkIp])
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-col h-screen overflow-hidden">
|
<div className="flex flex-col h-screen overflow-hidden">
|
||||||
{/* <button
|
{/* <button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
setCheckIp((prev) => !prev)
|
||||||
window.electron.connectVPN().then((msg) => {
|
window.electron.connectVPN().then((msg) => {
|
||||||
console.log(msg);
|
console.log(msg);
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
className="text-purple-400"
|
||||||
>
|
>
|
||||||
Connect VPN
|
Connect VPN
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -741,6 +754,7 @@ const DashboardLayout = () => {
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
window.electron.disconnectVPN();
|
window.electron.disconnectVPN();
|
||||||
}}
|
}}
|
||||||
|
className="text-red-400"
|
||||||
>
|
>
|
||||||
Disconnect VPN
|
Disconnect VPN
|
||||||
</button> */}
|
</button> */}
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,7 @@ const Dashboard: React.FC = () => {
|
||||||
const projectSummary = useSelector((state: any) => state.projects.projectSummary);
|
const projectSummary = useSelector((state: any) => state.projects.projectSummary);
|
||||||
const projectTrackingMap = useSelector((state: any) => state.projects.projectTrackingMap);
|
const projectTrackingMap = useSelector((state: any) => state.projects.projectTrackingMap);
|
||||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||||
|
const isLimitReached = selectedProject ? isProjectLimitReached(selectedProject) : false;
|
||||||
|
|
||||||
console.log("Line 111", projectsData, selectedProject, projectTrackingMap)
|
console.log("Line 111", projectsData, selectedProject, projectTrackingMap)
|
||||||
|
|
||||||
|
|
@ -129,14 +130,14 @@ const Dashboard: React.FC = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchProjects = async () => {
|
const fetchProjects = async () => {
|
||||||
setIsRefreshing(true)
|
setIsRefreshing(true);
|
||||||
try {
|
try {
|
||||||
console.log("📡 Fetching projects...Dashboard");
|
console.log("📡 Fetching projects...Dashboard");
|
||||||
const res = await window.electron.getProjectList();
|
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)) {
|
if (res?.success && Array.isArray(res.data)) {
|
||||||
// Store data redux store
|
// Store data in redux store
|
||||||
dispatch(setProjectsDataSlice(res.data));
|
dispatch(setProjectsDataSlice(res.data));
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
|
|
@ -151,35 +152,67 @@ const Dashboard: React.FC = () => {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// Auto-select first project
|
// Check if we already have a selected project that's being tracked
|
||||||
if (res.data.length > 0) {
|
const currentlySelected = selectedProject;
|
||||||
const firstProject = res.data[0];
|
const currentlyTracking = isTracking;
|
||||||
|
|
||||||
|
// Find if the currently selected project still exists in the fresh data
|
||||||
|
let projectToSelect = null;
|
||||||
|
|
||||||
console.log("Line 158" , firstProject)
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
// Select the project
|
||||||
dispatch(setSelectedProjectSlice(firstProject));
|
dispatch(setSelectedProjectSlice(projectToSelect));
|
||||||
|
|
||||||
const initialElapsed = parseTimeStringToSeconds(firstProject.totalWorkTime);
|
// Only initialize tracking if it doesn't exist already
|
||||||
|
const trackingExists = projectTrackingMap[projectToSelect.projectId];
|
||||||
// Initialize tracking data for first project
|
if (!trackingExists) {
|
||||||
|
const initialElapsed = parseTimeStringToSeconds(projectToSelect.totalWorkTime);
|
||||||
dispatch(
|
dispatch(
|
||||||
initializeProjectTracking({
|
initializeProjectTracking({
|
||||||
projectId: firstProject.projectId,
|
projectId: projectToSelect.projectId,
|
||||||
initialTime: initialElapsed,
|
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()
|
fetchProfile();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
console.warn("⚠️ Unexpected response:", res);
|
console.warn("⚠️ Unexpected response:", res);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("❌ Error fetching projects:", err);
|
console.error("❌ Error fetching projects:", err);
|
||||||
} finally {
|
} finally {
|
||||||
setIsRefreshing(false)
|
setIsRefreshing(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -246,7 +279,7 @@ const Dashboard: React.FC = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="flex-1 overflow-hidden no-scrollbar max-h-[85vh]">
|
<main className="flex-1 overflow-hidden no-scrollbar ">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
<h1 className="text-md font-bold">Dashboard</h1>
|
<h1 className="text-md font-bold">Dashboard</h1>
|
||||||
|
|
@ -272,6 +305,11 @@ const Dashboard: React.FC = () => {
|
||||||
<p className="text-lg font-extrabold text-success">
|
<p className="text-lg font-extrabold text-success">
|
||||||
{formatTime(displayTime)}
|
{formatTime(displayTime)}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
{/* <p className={`text-lg font-extrabold ${isLimitReached ? 'text-error' : 'text-success'}`}>
|
||||||
|
{formatTime(displayTime)}
|
||||||
|
{isLimitReached && " ⚠️"}
|
||||||
|
</p> */}
|
||||||
<div>
|
<div>
|
||||||
<span className="text-base-content/70">Project:</span>{" "}
|
<span className="text-base-content/70">Project:</span>{" "}
|
||||||
<span className="font-semibold">
|
<span className="font-semibold">
|
||||||
|
|
@ -330,15 +368,16 @@ const Dashboard: React.FC = () => {
|
||||||
{/* 🔹 Sticky Header */}
|
{/* 🔹 Sticky Header */}
|
||||||
<div className="sticky top-0 z-20 bg-base-100 border-b border-base-content/10 p-3 flex justify-between items-center">
|
<div className="sticky top-0 z-20 bg-base-100 border-b border-base-content/10 p-3 flex justify-between items-center">
|
||||||
<p className="text-md font-bold">Projects</p>
|
<p className="text-md font-bold">Projects</p>
|
||||||
|
{!selectedData?.isTracking &&
|
||||||
<button
|
<button
|
||||||
onClick={handleRefresh}
|
onClick={handleRefresh}
|
||||||
disabled={isRefreshing}
|
disabled={isRefreshing }
|
||||||
className="btn btn-xs btn-ghost gap-1 text-warning"
|
className="btn btn-xs btn-ghost gap-1 text-warning"
|
||||||
title="Refresh projects"
|
title="Refresh projects"
|
||||||
>
|
>
|
||||||
<RefreshCw className={`w-3 h-3 text-warning ${isRefreshing ? 'animate-spin' : ''}`} />
|
<RefreshCw className={`w-3 h-3 text-warning ${isRefreshing ? 'animate-spin' : ''}`} />
|
||||||
Refresh
|
Refresh
|
||||||
</button>
|
</button>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 🔹 Table or Empty State or Skeleton */}
|
{/* 🔹 Table or Empty State or Skeleton */}
|
||||||
|
|
@ -362,7 +401,7 @@ const Dashboard: React.FC = () => {
|
||||||
minHeight: "auto",
|
minHeight: "auto",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<table className="table table-zebra w-full text-[12px]">
|
<table className="table table-zebra w-full text-[12px] ">
|
||||||
<thead className="sticky top-0 bg-base-200 z-10">
|
<thead className="sticky top-0 bg-base-200 z-10">
|
||||||
<tr>
|
<tr>
|
||||||
<th>#</th>
|
<th>#</th>
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ const HelpSupport: React.FC = () => {
|
||||||
<Mail size={18} className="text-primary" />
|
<Mail size={18} className="text-primary" />
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-base-content/70">Email</p>
|
<p className="text-xs text-base-content/70">Email</p>
|
||||||
<p className="font-medium text-sm">support@vatechcorp.com</p>
|
<p className="font-medium text-sm">support@docva.com</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -31,7 +31,7 @@ const HelpSupport: React.FC = () => {
|
||||||
<Phone size={18} className="text-primary" />
|
<Phone size={18} className="text-primary" />
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-base-content/70">Phone</p>
|
<p className="text-xs text-base-content/70">Phone</p>
|
||||||
<p className="font-medium text-sm">+91 98765 43210</p>
|
<p className="font-medium text-sm">+XXXX XXXXX</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -464,7 +464,7 @@ const AdjustmentTab: React.FC<AdjustmentTabProps> = ({ projects }) => {
|
||||||
console.log("Timesheet Created Successfully");
|
console.log("Timesheet Created Successfully");
|
||||||
resetForm();
|
resetForm();
|
||||||
// Show success toast instead of alert
|
// Show success toast instead of alert
|
||||||
showToast("PTO Request submitted successfully!", "success");
|
showToast("Timesheet Request submitted successfully!", "success");
|
||||||
|
|
||||||
// Navigate after a small delay
|
// Navigate after a small delay
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
@ -681,3 +681,4 @@ const AdjustmentTab: React.FC<AdjustmentTabProps> = ({ projects }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AdjustmentTab;
|
export default AdjustmentTab;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ const PaysSlips = () => {
|
||||||
<th>Date</th>
|
<th>Date</th>
|
||||||
<th>Total Hours</th>
|
<th>Total Hours</th>
|
||||||
<th>Total Pay</th>
|
<th>Total Pay</th>
|
||||||
<th>Action</th>
|
{/* <th>Action</th> */}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
|
|
@ -111,13 +111,13 @@ const PaysSlips = () => {
|
||||||
|
|
||||||
<td>${row.totalPayAmount.toFixed(2)}</td>
|
<td>${row.totalPayAmount.toFixed(2)}</td>
|
||||||
|
|
||||||
<td>
|
{/* <td>
|
||||||
<button
|
<button
|
||||||
className="btn btn-xs btn-primary"
|
className="btn btn-xs btn-primary"
|
||||||
>
|
>
|
||||||
View
|
View
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td> */}
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// src/ui/pages/Settings/ChangePasswordModal.tsx
|
// src/ui/pages/Settings/ChangePasswordModal.tsx
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { Eye, EyeOff } from "lucide-react";
|
import { Eye, EyeOff } from "lucide-react";
|
||||||
|
import { useToast } from "../../helper/ToastContext";
|
||||||
|
|
||||||
interface ChangePasswordModalProps {
|
interface ChangePasswordModalProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
|
|
@ -19,12 +20,13 @@ const ChangePasswordModal: React.FC<ChangePasswordModalProps> = ({
|
||||||
const [showOld, setShowOld] = useState(false);
|
const [showOld, setShowOld] = useState(false);
|
||||||
const [showNew, setShowNew] = useState(false);
|
const [showNew, setShowNew] = useState(false);
|
||||||
const [showConfirm, setShowConfirm] = useState(false);
|
const [showConfirm, setShowConfirm] = useState(false);
|
||||||
|
const { showToast } = useToast();
|
||||||
|
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (newPassword !== confirmPassword) {
|
if (newPassword !== confirmPassword) {
|
||||||
alert("New passwords do not match!");
|
showToast("New passwords do not match!" , "error");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -40,17 +42,17 @@ const ChangePasswordModal: React.FC<ChangePasswordModalProps> = ({
|
||||||
const response = await window.electron.changePassword(payload);
|
const response = await window.electron.changePassword(payload);
|
||||||
|
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
alert("Password changed successfully!");
|
showToast("Password changed successfully!" , "success");
|
||||||
setOldPassword("")
|
setOldPassword("")
|
||||||
setNewPassword("")
|
setNewPassword("")
|
||||||
setConfirmPassword("")
|
setConfirmPassword("")
|
||||||
onClose();
|
onClose();
|
||||||
} else {
|
} else {
|
||||||
alert(response.message || "Failed to change password.");
|
showToast((response.message || "Failed to change password.") , "error");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Password change error:", error);
|
console.error("Password change error:", error);
|
||||||
alert("Error changing password.");
|
showToast("Error changing password." , "error");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { DateTime } from "luxon";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
interface TabActivity {
|
interface TabActivity {
|
||||||
|
|
@ -27,6 +28,21 @@ interface Props {
|
||||||
const ProjectLogsModal: React.FC<Props> = ({ isOpen, onClose, projectName, logs, loading }) => {
|
const ProjectLogsModal: React.FC<Props> = ({ isOpen, onClose, projectName, logs, loading }) => {
|
||||||
if (!isOpen) return null;
|
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 (
|
return (
|
||||||
<dialog className="modal modal-open">
|
<dialog className="modal modal-open">
|
||||||
<div className="modal-box max-h-[97vh] max-w-4xl bg-base-100 flex flex-col">
|
<div className="modal-box max-h-[97vh] max-w-4xl bg-base-100 flex flex-col">
|
||||||
|
|
@ -46,7 +62,11 @@ const ProjectLogsModal: React.FC<Props> = ({ isOpen, onClose, projectName, logs,
|
||||||
<div key={idx} className="border border-base-300 overflow-hidden">
|
<div key={idx} className="border border-base-300 overflow-hidden">
|
||||||
<div className="bg-base-200 p-2 font-semibold flex justify-between items-center">
|
<div className="bg-base-200 p-2 font-semibold flex justify-between items-center">
|
||||||
<span className="text-[12px]">
|
<span className="text-[12px]">
|
||||||
🕒 Session {idx + 1} — {session.totalSessionTime}
|
🕒 Session {idx + 1} — {getTimeDifference(
|
||||||
|
new Date(session.from).toISOString(),
|
||||||
|
new Date(session.to).toISOString()
|
||||||
|
).formatted}
|
||||||
|
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xs text-gray-500">
|
<span className="text-xs text-gray-500">
|
||||||
{new Date(session.from).toLocaleTimeString()} -{" "}
|
{new Date(session.from).toLocaleTimeString()} -{" "}
|
||||||
|
|
@ -69,10 +89,10 @@ const ProjectLogsModal: React.FC<Props> = ({ isOpen, onClose, projectName, logs,
|
||||||
<tr key={tabIdx} className="text-[10px]">
|
<tr key={tabIdx} className="text-[10px]">
|
||||||
<td>{tabIdx + 1}</td>
|
<td>{tabIdx + 1}</td>
|
||||||
<td className="truncate max-w-[150px]" title={tab.title}>
|
<td className="truncate max-w-[150px]" title={tab.title}>
|
||||||
{tab.title} <br/>
|
{tab.title} <br />
|
||||||
<span className="text-slate-500">{tab.appName}</span>
|
<span className="text-slate-500">{tab.appName}</span>
|
||||||
</td>
|
</td>
|
||||||
<td><span className="text-green-500">{new Date(tab.from).toLocaleTimeString()}</span> <br/><span className="text-red-600">{new Date(tab.to).toLocaleTimeString()}</span></td>
|
<td><span className="text-green-500">{new Date(tab.from).toLocaleTimeString()}</span> <br /><span className="text-red-600">{new Date(tab.to).toLocaleTimeString()}</span></td>
|
||||||
<td>{tab.durationFormatted}</td>
|
<td>{tab.durationFormatted}</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -52,10 +52,11 @@ const Timesheets: React.FC = () => {
|
||||||
|
|
||||||
const { windowLogs } = useSelector((state: RootState) => state.window);
|
const { windowLogs } = useSelector((state: RootState) => state.window);
|
||||||
|
|
||||||
|
|
||||||
console.log(" Timesheet in component:", windowLogs);
|
console.log(" Timesheet in component:", windowLogs);
|
||||||
const [projectsData, setProjectsData] = useState<Project[]>([]);
|
const [projectsData, setProjectsData] = useState<Project[]>([]);
|
||||||
const [logs, setLogs] = useState([]);
|
const [logs, setLogs] = useState([]);
|
||||||
const [selectedProject, setSelectedProject] = useState<string | null>(null);
|
// const [selectedProject, setSelectedProject] = useState<string | null>(null);
|
||||||
const [selectedProjectName, setSelectedProjectName] = useState<string>("");
|
const [selectedProjectName, setSelectedProjectName] = useState<string>("");
|
||||||
const [modalOpen, setModalOpen] = useState(false);
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
@ -63,9 +64,13 @@ const Timesheets: React.FC = () => {
|
||||||
const [lastRefreshed, setLastRefreshed] = useState<Date | null>(null);
|
const [lastRefreshed, setLastRefreshed] = useState<Date | null>(null);
|
||||||
const [initialLoading, setInitialLoading] = useState(true);
|
const [initialLoading, setInitialLoading] = useState(true);
|
||||||
const [tick, setTick] = useState(0);
|
const [tick, setTick] = useState(0);
|
||||||
|
const { selectedProject } =
|
||||||
|
useSelector((state: any) => state.projects);
|
||||||
|
|
||||||
const activeProjects = projectsData.filter(p => p.status === "ACTIVE");
|
const activeProjects = projectsData.filter(p => p.status === "ACTIVE");
|
||||||
|
|
||||||
|
console.log("selectedProject", selectedProject.projectId)
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
|
|
@ -84,7 +89,6 @@ const Timesheets: React.FC = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const fetchProjects = async () => {
|
const fetchProjects = async () => {
|
||||||
setInitialLoading(true);
|
setInitialLoading(true);
|
||||||
try {
|
try {
|
||||||
|
|
@ -120,7 +124,7 @@ const Timesheets: React.FC = () => {
|
||||||
try {
|
try {
|
||||||
setModalOpen(true);
|
setModalOpen(true);
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setSelectedProject(projectID);
|
// setSelectedProject(projectID);
|
||||||
setSelectedProjectName(projectName);
|
setSelectedProjectName(projectName);
|
||||||
|
|
||||||
console.log("projectID", projectID)
|
console.log("projectID", projectID)
|
||||||
|
|
@ -154,10 +158,6 @@ const Timesheets: React.FC = () => {
|
||||||
return parts.slice(0, 2).join(" ");
|
return parts.slice(0, 2).join(" ");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Skeleton loader rows
|
// Skeleton loader rows
|
||||||
const renderSkeletonRows = () => {
|
const renderSkeletonRows = () => {
|
||||||
return Array.from({ length: 5 }).map((_, index) => (
|
return Array.from({ length: 5 }).map((_, index) => (
|
||||||
|
|
@ -225,13 +225,13 @@ const Timesheets: React.FC = () => {
|
||||||
<td>{formatWorkTime(entry.totalWorkTime)}</td>
|
<td>{formatWorkTime(entry.totalWorkTime)}</td>
|
||||||
<td>
|
<td>
|
||||||
<button
|
<button
|
||||||
disabled={formatWorkTime(entry.totalWorkTime) === "0h 0m"}
|
disabled={(formatWorkTime(entry.totalWorkTime) === "0h 0m" || (isTracking && entry.projectId === selectedProject.projectId))}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
handleViewLogs(entry.projectId, entry.projectName)
|
handleViewLogs(entry.projectId, entry.projectName)
|
||||||
}
|
}
|
||||||
className="btn btn-ghost btn-xs"
|
className="btn btn-ghost btn-xs"
|
||||||
>
|
>
|
||||||
{formatWorkTime(entry.totalWorkTime) === "0h 0m" ? (
|
{(formatWorkTime(entry.totalWorkTime) === "0h 0m" || (isTracking && entry.projectId === selectedProject.projectId)) ? (
|
||||||
<EyeOff size={16} />
|
<EyeOff size={16} />
|
||||||
) : (
|
) : (
|
||||||
<Eye size={16} />
|
<Eye size={16} />
|
||||||
|
|
|
||||||
|
|
@ -1,54 +1,154 @@
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { Shield, ShieldAlert, RefreshCw, Wifi } from "lucide-react";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const VpnGuard: React.FC<Props> = ({ children }) => {
|
const VpnGuard: React.FC<Props> = ({ children }) => {
|
||||||
|
|
||||||
|
const VPN_API = "http://10.0.0.2:3000";
|
||||||
|
|
||||||
const [checking, setChecking] = useState(true);
|
const [checking, setChecking] = useState(true);
|
||||||
const [vpnConnected, setVpnConnected] = useState(true);
|
const [vpnConnected, setVpnConnected] = useState(true);
|
||||||
|
const [isRetrying, setIsRetrying] = useState(false);
|
||||||
|
|
||||||
// const checkVPN = async () => {
|
const checkVPN = async () => {
|
||||||
// try {
|
try {
|
||||||
// const res = await window.electron.checkVpnConnection();
|
setChecking(true);
|
||||||
// setVpnConnected(res?.success);
|
|
||||||
// } catch {
|
const res = await window.electron?.checkRegion();
|
||||||
// setVpnConnected(false);
|
console.log("VpnGuard", res);
|
||||||
// } finally {
|
|
||||||
// setChecking(false);
|
setVpnConnected(res?.success === true);
|
||||||
// }
|
} catch (error) {
|
||||||
// };
|
console.error("VPN check failed:", error);
|
||||||
|
setVpnConnected(false);
|
||||||
|
} finally {
|
||||||
|
setChecking(false);
|
||||||
|
setIsRetrying(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// checkVPN();
|
checkVPN();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (checking) {
|
const connectVPN = async () => {
|
||||||
|
setChecking(true);
|
||||||
|
try {
|
||||||
|
const res = await window.electron.connectVPN();
|
||||||
|
console.log("connectVPN result → ", res);
|
||||||
|
|
||||||
|
// Only proceed if VPN actually connected
|
||||||
|
if (res === "Windows VPN connected silently") {
|
||||||
|
|
||||||
|
const vpnRes = await window.electron.updateBaseURL(VPN_API);
|
||||||
|
console.log("vpnRes", vpnRes);
|
||||||
|
|
||||||
|
if (vpnRes) {
|
||||||
|
// Run VPN check again AFTER URL changes
|
||||||
|
await checkVPN();
|
||||||
|
} else {
|
||||||
|
console.warn("Base URL updated but health check failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
setChecking(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Checking state
|
||||||
|
if (checking && !isRetrying) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex justify-center items-center">
|
<div className="min-h-screen flex flex-col justify-center items-center bg-base-200">
|
||||||
<span className="loading loading-spinner loading-lg"></span>
|
<div className="relative">
|
||||||
<span className="ml-3">Checking VPN...</span>
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
|
<div className="w-24 h-24 border-4 border-primary opacity-20 rounded-full animate-ping"></div>
|
||||||
|
</div>
|
||||||
|
<div className="relative bg-base-100 rounded-full p-6 shadow-xl">
|
||||||
|
<Shield className="w-12 h-12 text-primary animate-pulse" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-8 text-center">
|
||||||
|
<h2 className="text-2xl font-semibold mb-2">
|
||||||
|
Verifying Connection
|
||||||
|
</h2>
|
||||||
|
<p className="text-base-content opacity-70">Checking VPN status...</p>
|
||||||
|
<div className="flex justify-center mt-4 gap-1">
|
||||||
|
<span className="loading loading-dots loading-md text-primary"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VPN not connected state
|
||||||
if (!vpnConnected) {
|
if (!vpnConnected) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex justify-center items-center text-center">
|
<div className="min-h-screen flex flex-col justify-center items-center bg-base-200 p-4">
|
||||||
<div className="card shadow-xl bg-base-100 p-6">
|
<div className="max-w-md w-full">
|
||||||
<h2 className="text-xl font-bold text-red-600">VPN Required</h2>
|
<div className="card bg-base-100 shadow-2xl">
|
||||||
<p className="mt-2">Please connect to your company VPN to continue.</p>
|
<div className="card-body items-center text-center">
|
||||||
|
{/* Icon with animation */}
|
||||||
|
<div className="relative mb-4">
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
|
<div className="w-20 h-20 border-4 border-error opacity-20 rounded-full animate-pulse"></div>
|
||||||
|
</div>
|
||||||
|
<div className="relative bg-error bg-opacity-10 rounded-full p-4">
|
||||||
|
<ShieldAlert className="w-12 h-12" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button className="btn btn-primary mt-4"
|
{/* Title */}
|
||||||
onClick={() => window.location.reload()}>
|
<h2 className="card-title text-3xl mb-3">
|
||||||
Retry
|
VPN Required
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
{/* Description */}
|
||||||
|
<p className="mb-6 opacity-80">
|
||||||
|
Please connect to your company VPN to access this application.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Retry button */}
|
||||||
|
<button
|
||||||
|
className="btn btn-primary btn-wide gap-2"
|
||||||
|
onClick={connectVPN}
|
||||||
|
disabled={isRetrying}
|
||||||
|
>
|
||||||
|
{isRetrying ? (
|
||||||
|
<>
|
||||||
|
<span className="loading loading-spinner loading-sm"></span>
|
||||||
|
Checking Connection...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<RefreshCw className="w-5 h-5" />
|
||||||
|
Connect VPN
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{/* Help text */}
|
||||||
|
<div className="divider"></div>
|
||||||
|
<p className="text-xs opacity-60">
|
||||||
|
Need help? Contact IT support at{" "}
|
||||||
|
<a href="mailto:support@company.com" className="link link-primary">
|
||||||
|
support@company.com
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default VpnGuard;
|
export default VpnGuard;
|
||||||
|
|
@ -133,6 +133,15 @@ interface PayslipResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interface RegionResponse {
|
||||||
|
success: boolean;
|
||||||
|
status: number;
|
||||||
|
data?: any;
|
||||||
|
retry?: boolean;
|
||||||
|
message?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
interface Window {
|
interface Window {
|
||||||
electron: {
|
electron: {
|
||||||
login: (credentials: { email: string; password: string }) => Promise<any>;
|
login: (credentials: { email: string; password: string }) => Promise<any>;
|
||||||
|
|
@ -242,8 +251,14 @@ interface Window {
|
||||||
data?: any;
|
data?: any;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
checkRegion: () => Promise<RegionResponse>;
|
||||||
connectVPN: () => Promise<string>;
|
connectVPN: () => Promise<string>;
|
||||||
disconnectVPN: () => Promise<void>;
|
disconnectVPN: () => Promise<void>;
|
||||||
|
getBaseURL: () => Promise<string>;
|
||||||
|
updateBaseURL: (url: string) => Promise<{
|
||||||
|
updated: boolean;
|
||||||
|
baseURL: string;
|
||||||
|
}>;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue