Compare commits

..

No commits in common. "85a178e8251407cf1932566c97b6183aba16812a" and "394150a2ee5454d7769355a85675a51ae24773aa" have entirely different histories.

27 changed files with 431 additions and 1348 deletions

0
.env
View File

View File

@ -1,7 +1,6 @@
[Interface] [Interface]
PrivateKey = OPasvZsdAdU46BJLXOLMqy4BgZq/UvKtjTRpTTVSGX8= PrivateKey = CEzCd25ufxbOjvRhMqLfkcdP/OziG5i6OkXCfMZrI1I=
Address = 10.0.0.96/32 Address = 10.0.0.2/24
DNS = 1.1.1.1 DNS = 1.1.1.1
[Peer] [Peer]

View File

@ -1,6 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<style> <style>
body { body {
@ -38,18 +37,14 @@
align-items: center; align-items: center;
} }
.loader-logo svg { .loader-logo img {
width: 70px; width: 45px;
height: 70px; height: 45px;
margin-left: 12px;
margin-top: 8px;
object-fit: contain; object-fit: contain;
} }
@keyframes spin { @keyframes spin {
to { to { transform: rotate(360deg); }
transform: rotate(360deg);
}
} }
</style> </style>
</head> </head>
@ -60,266 +55,8 @@
<!-- Place your logo here --> <!-- Place your logo here -->
<div class="loader-logo"> <div class="loader-logo">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100" <img src="your-logo.png" alt="App Logo">
zoomAndPan="magnify" viewBox="0 0 368.25 242.999999" height="100" preserveAspectRatio="xMidYMid meet"
version="1.0">
<defs>
<clipPath id="17e5ee130b">
<path d="M 137.171875 1.269531 L 231 1.269531 L 231 99 L 137.171875 99 Z M 137.171875 1.269531 "
clip-rule="nonzero" />
</clipPath>
<clipPath id="0262fc8075">
<path d="M 284 124 L 364.921875 124 L 364.921875 206 L 284 206 Z M 284 124 " clip-rule="nonzero" />
</clipPath>
<clipPath id="0914a8505c">
<path d="M 3 231 L 14 231 L 14 241.671875 L 3 241.671875 Z M 3 231 " clip-rule="nonzero" />
</clipPath>
<clipPath id="da83339c1d">
<path d="M 17 231 L 20 231 L 20 241.671875 L 17 241.671875 Z M 17 231 " clip-rule="nonzero" />
</clipPath>
<clipPath id="97dbddcd78">
<path d="M 25 231 L 34 231 L 34 241.671875 L 25 241.671875 Z M 25 231 " clip-rule="nonzero" />
</clipPath>
<clipPath id="3223358027">
<path d="M 36 231 L 46 231 L 46 241.671875 L 36 241.671875 Z M 36 231 " clip-rule="nonzero" />
</clipPath>
<clipPath id="f960afe448">
<path d="M 49 231 L 59 231 L 59 241.671875 L 49 241.671875 Z M 49 231 " clip-rule="nonzero" />
</clipPath>
<clipPath id="04b92ea6b9">
<path d="M 61 231 L 72 231 L 72 241.671875 L 61 241.671875 Z M 61 231 " clip-rule="nonzero" />
</clipPath>
<clipPath id="7b4adb9303">
<path d="M 75 231 L 84 231 L 84 241.671875 L 75 241.671875 Z M 75 231 " clip-rule="nonzero" />
</clipPath>
<clipPath id="53357fd403">
<path d="M 93 231 L 104 231 L 104 241.671875 L 93 241.671875 Z M 93 231 " clip-rule="nonzero" />
</clipPath>
<clipPath id="bcd03dbca6">
<path d="M 106 231 L 117 231 L 117 241.671875 L 106 241.671875 Z M 106 231 " clip-rule="nonzero" />
</clipPath>
<clipPath id="ff8639fe11">
<path d="M 120 231 L 130 231 L 130 241.671875 L 120 241.671875 Z M 120 231 " clip-rule="nonzero" />
</clipPath>
<clipPath id="3990de54cb">
<path d="M 134 231 L 137 231 L 137 241.671875 L 134 241.671875 Z M 134 231 " clip-rule="nonzero" />
</clipPath>
<clipPath id="045cbd7804">
<path d="M 141 231 L 151 231 L 151 241.671875 L 141 241.671875 Z M 141 231 " clip-rule="nonzero" />
</clipPath>
<clipPath id="7de9a85e20">
<path d="M 154 231 L 163 231 L 163 241.671875 L 154 241.671875 Z M 154 231 " clip-rule="nonzero" />
</clipPath>
<clipPath id="e68f5b3fba">
<path d="M 165 231 L 176 231 L 176 241.671875 L 165 241.671875 Z M 165 231 " clip-rule="nonzero" />
</clipPath>
<clipPath id="65464ab719">
<path d="M 179 231 L 189 231 L 189 241.671875 L 179 241.671875 Z M 179 231 " clip-rule="nonzero" />
</clipPath>
<clipPath id="b82da71650">
<path d="M 192 231 L 202 231 L 202 241.671875 L 192 241.671875 Z M 192 231 " clip-rule="nonzero" />
</clipPath>
<clipPath id="fe4eaf6826">
<path d="M 204 231 L 215 231 L 215 241.671875 L 204 241.671875 Z M 204 231 " clip-rule="nonzero" />
</clipPath>
<clipPath id="79d174fbbf">
<path d="M 225 231 L 234 231 L 234 241.671875 L 225 241.671875 Z M 225 231 " clip-rule="nonzero" />
</clipPath>
<clipPath id="b0be87ffda">
<path d="M 236 230 L 249 230 L 249 241.671875 L 236 241.671875 Z M 236 230 " clip-rule="nonzero" />
</clipPath>
<clipPath id="444dbef42a">
<path d="M 252 231 L 262 231 L 262 241.671875 L 252 241.671875 Z M 252 231 " clip-rule="nonzero" />
</clipPath>
<clipPath id="314903e6e8">
<path d="M 272 230 L 283 230 L 283 241.671875 L 272 241.671875 Z M 272 230 " clip-rule="nonzero" />
</clipPath>
<clipPath id="2fd70bc44d">
<path d="M 286 230 L 299 230 L 299 241.671875 L 286 241.671875 Z M 286 230 " clip-rule="nonzero" />
</clipPath>
<clipPath id="032e9be536">
<path d="M 302 230 L 312 230 L 312 241.671875 L 302 241.671875 Z M 302 230 " clip-rule="nonzero" />
</clipPath>
<clipPath id="77ab9ec736">
<path d="M 314 231 L 324 231 L 324 241.671875 L 314 241.671875 Z M 314 231 " clip-rule="nonzero" />
</clipPath>
<clipPath id="bf7e46828b">
<path d="M 326 230 L 339 230 L 339 241.671875 L 326 241.671875 Z M 326 230 " clip-rule="nonzero" />
</clipPath>
<clipPath id="b78234ca4f">
<path d="M 343 231 L 352 231 L 352 241.671875 L 343 241.671875 Z M 343 231 " clip-rule="nonzero" />
</clipPath>
<clipPath id="2b0e76334b">
<path d="M 355 231 L 364.921875 231 L 364.921875 241.671875 L 355 241.671875 Z M 355 231 "
clip-rule="nonzero" />
</clipPath>
</defs>
<g clip-path="url(#17e5ee130b)">
<path fill="#060165"
d="M 181.777344 1.269531 L 137.171875 1.269531 L 137.171875 53.847656 C 137.171875 78.601562 157.222656 98.652344 181.980469 98.652344 C 208.785156 98.652344 230.679688 76.761719 230.679688 49.960938 C 230.679688 23.160156 208.785156 1.269531 181.777344 1.269531 "
fill-opacity="1" fill-rule="nonzero" />
</g>
<path fill="#ffffff"
d="M 173.386719 31.140625 C 173.386719 36.253906 169.089844 40.550781 163.976562 40.550781 C 158.859375 40.550781 154.5625 44.847656 154.5625 49.960938 C 154.5625 55.074219 158.859375 59.371094 163.976562 59.371094 L 182.800781 59.371094 C 187.914062 59.371094 192.210938 55.074219 192.210938 49.960938 L 192.210938 31.140625 C 192.210938 26.023438 187.914062 21.726562 182.800781 21.726562 C 177.683594 21.726562 173.386719 26.023438 173.386719 31.140625 "
fill-opacity="1" fill-rule="nonzero" />
<path fill="#05c3ec"
d="M 194.257812 68.78125 C 194.257812 63.667969 198.554688 59.371094 203.671875 59.371094 C 208.785156 59.371094 213.082031 55.074219 213.082031 49.960938 C 213.082031 44.847656 208.785156 40.550781 203.671875 40.550781 L 184.84375 40.550781 C 179.730469 40.550781 175.433594 44.847656 175.433594 49.960938 L 175.433594 68.78125 C 175.433594 73.898438 179.730469 78.191406 184.84375 78.191406 C 189.960938 78.191406 194.257812 73.898438 194.257812 68.78125 "
fill-opacity="1" fill-rule="nonzero" />
<path fill="#060165"
d="M 3.140625 205.640625 L 3.140625 124.460938 L 34.589844 124.460938 C 40.664062 124.460938 46.304688 125.542969 51.292969 127.5 C 56.28125 129.453125 60.835938 132.273438 64.304688 135.746094 C 67.992188 139.21875 70.8125 143.558594 72.765625 148.550781 C 74.71875 153.546875 75.800781 158.972656 75.800781 165.050781 C 75.800781 171.125 74.71875 176.554688 72.765625 181.546875 C 70.8125 186.539062 67.992188 190.878906 64.304688 194.351562 C 60.617188 198.042969 56.28125 200.648438 51.292969 202.816406 C 46.304688 204.769531 40.664062 205.855469 34.589844 205.855469 L 3.140625 205.855469 Z M 18.976562 190.226562 L 34.375 190.226562 C 38.0625 190.226562 41.53125 189.578125 44.570312 188.492188 C 47.605469 187.1875 50.207031 185.667969 52.378906 183.5 C 54.546875 181.328125 56.28125 178.722656 57.367188 175.46875 C 58.449219 172.429688 59.101562 168.957031 59.101562 165.050781 C 59.101562 161.140625 58.449219 157.886719 57.367188 154.628906 C 56.28125 151.589844 54.546875 148.988281 52.378906 146.816406 C 50.207031 144.644531 47.605469 142.910156 44.570312 141.824219 C 41.53125 140.738281 38.0625 140.085938 34.375 140.085938 L 18.976562 140.085938 C 18.976562 140.085938 18.976562 190.226562 18.976562 190.226562 Z M 18.976562 190.226562 "
fill-opacity="1" fill-rule="nonzero" />
<path fill="#060165"
d="M 84.695312 173.949219 C 84.695312 169.171875 85.5625 164.832031 87.296875 160.925781 C 89.03125 157.019531 91.417969 153.546875 94.238281 150.507812 C 97.273438 147.46875 100.746094 145.296875 104.648438 143.558594 C 108.769531 141.824219 113.109375 141.171875 117.664062 141.171875 C 122.21875 141.171875 126.773438 142.039062 130.675781 143.558594 C 134.796875 145.296875 138.265625 147.46875 141.304688 150.507812 C 144.339844 153.546875 146.726562 157.019531 148.246094 160.925781 C 149.980469 164.832031 150.847656 169.171875 150.847656 173.949219 C 150.847656 178.507812 149.980469 182.847656 148.246094 186.753906 C 146.507812 190.660156 144.125 194.136719 141.304688 197.171875 C 138.265625 200.210938 134.796875 202.382812 130.675781 204.121094 C 126.554688 205.855469 122.21875 206.507812 117.664062 206.507812 C 113.109375 206.507812 108.554688 205.640625 104.648438 204.121094 C 100.527344 202.382812 97.058594 200.210938 94.019531 197.171875 C 90.984375 194.136719 88.816406 190.660156 87.082031 186.753906 C 85.34375 182.847656 84.695312 178.722656 84.695312 173.949219 M 100.527344 173.949219 C 100.527344 176.335938 100.960938 178.507812 101.828125 180.675781 C 102.695312 182.847656 103.78125 184.800781 105.300781 186.320312 C 106.816406 187.839844 108.554688 189.140625 110.722656 190.226562 C 112.890625 191.097656 115.277344 191.746094 117.878906 191.746094 C 120.480469 191.746094 122.867188 191.3125 124.820312 190.226562 C 126.988281 189.359375 128.722656 188.058594 130.242188 186.320312 C 131.761719 184.800781 132.84375 182.847656 133.710938 180.675781 C 134.582031 178.507812 135.015625 176.335938 135.015625 173.949219 C 135.015625 171.5625 134.582031 169.390625 133.710938 167.21875 C 132.84375 165.050781 131.761719 163.09375 130.242188 161.578125 C 128.722656 160.058594 126.988281 158.753906 124.820312 157.667969 C 122.652344 156.800781 120.480469 156.148438 117.878906 156.148438 C 115.277344 156.148438 112.890625 156.582031 110.722656 157.667969 C 108.554688 158.539062 106.816406 159.839844 105.300781 161.578125 C 103.78125 163.09375 102.695312 165.050781 101.828125 167.21875 C 100.960938 169.390625 100.527344 171.777344 100.527344 173.949219 "
fill-opacity="1" fill-rule="nonzero" />
<path fill="#060165"
d="M 159.523438 173.949219 C 159.523438 169.171875 160.390625 164.832031 162.125 160.925781 C 163.859375 157.019531 166.246094 153.546875 169.066406 150.507812 C 172.101562 147.46875 175.574219 145.296875 179.476562 143.558594 C 183.597656 141.824219 187.9375 141.171875 192.492188 141.171875 C 196.613281 141.171875 200.515625 141.824219 203.988281 143.125 C 207.671875 144.429688 210.710938 146.164062 213.53125 148.335938 C 216.347656 150.507812 218.734375 153.328125 220.46875 156.367188 C 222.421875 159.621094 223.507812 163.09375 224.15625 166.785156 L 207.890625 166.785156 C 206.804688 163.53125 205.070312 161.140625 202.25 159.1875 C 199.648438 157.234375 196.394531 156.148438 192.492188 156.148438 C 189.886719 156.148438 187.503906 156.582031 185.332031 157.667969 C 183.164062 158.539062 181.429688 160.058594 179.910156 161.578125 C 178.394531 163.3125 177.308594 165.050781 176.441406 167.21875 C 175.574219 169.390625 175.140625 171.5625 175.140625 174.164062 C 175.140625 176.554688 175.574219 178.722656 176.441406 180.894531 C 177.308594 183.066406 178.394531 185.019531 179.910156 186.539062 C 181.429688 188.273438 183.164062 189.578125 185.332031 190.445312 C 187.503906 191.3125 189.886719 191.964844 192.492188 191.964844 C 196.179688 191.964844 199.433594 191.097656 202.25 189.140625 C 204.855469 187.1875 206.804688 184.800781 207.890625 181.546875 L 224.15625 181.546875 C 223.507812 185.234375 222.207031 188.707031 220.46875 191.746094 C 218.519531 194.785156 216.347656 197.609375 213.53125 199.777344 C 210.710938 201.949219 207.457031 203.683594 203.988281 204.988281 C 200.300781 206.289062 196.613281 206.941406 192.492188 206.941406 C 187.71875 206.941406 183.382812 206.074219 179.476562 204.554688 C 175.355469 202.816406 171.886719 200.648438 169.066406 197.609375 C 166.03125 194.570312 163.859375 191.097656 162.125 187.1875 C 160.390625 182.847656 159.523438 178.722656 159.523438 173.949219 "
fill-opacity="1" fill-rule="nonzero" />
<path fill="#060165"
d="M 238.253906 124.460938 L 261.03125 187.1875 L 284.019531 124.460938 L 301.15625 124.460938 L 271.007812 205.640625 L 251.269531 205.640625 L 221.121094 124.460938 Z M 238.253906 124.460938 "
fill-opacity="1" fill-rule="nonzero" />
<g clip-path="url(#0262fc8075)">
<path fill="#060165"
d="M 315.035156 124.460938 L 334.558594 124.460938 L 364.921875 205.640625 L 348.65625 205.640625 L 343.015625 190.226562 L 307.011719 190.226562 L 301.15625 205.640625 L 284.886719 205.640625 Z M 338.246094 177.203125 L 325.015625 141.605469 L 311.78125 177.203125 Z M 338.246094 177.203125 "
fill-opacity="1" fill-rule="nonzero" />
</g>
<g clip-path="url(#0914a8505c)">
<path fill="#060165"
d="M 7.261719 241.453125 L 3.359375 231.035156 L 5.3125 231.035156 L 8.5625 239.71875 L 11.816406 231.035156 L 13.554688 231.035156 L 9.648438 241.453125 Z M 7.261719 241.453125 "
fill-opacity="1" fill-rule="nonzero" />
</g>
<g clip-path="url(#da83339c1d)">
<path fill="#060165"
d="M 17.675781 231.035156 L 19.410156 231.035156 L 19.410156 241.671875 L 17.675781 241.671875 Z M 17.675781 231.035156 "
fill-opacity="1" fill-rule="nonzero" />
</g>
<g clip-path="url(#97dbddcd78)">
<path fill="#060165"
d="M 25.046875 241.453125 L 25.046875 231.035156 L 29.601562 231.035156 C 31.773438 231.035156 33.722656 231.902344 33.722656 234.289062 C 33.722656 235.808594 32.855469 236.679688 31.773438 237.113281 C 32.421875 237.546875 32.640625 238.199219 32.855469 238.847656 C 33.074219 240.152344 33.507812 241.238281 33.722656 241.453125 L 31.773438 241.453125 C 31.554688 241.019531 31.339844 240.367188 31.121094 239.28125 C 30.90625 238.199219 30.472656 237.328125 29.601562 237.328125 L 27 237.328125 L 27 241.453125 Z M 26.785156 235.808594 L 29.167969 235.808594 C 30.472656 235.808594 31.773438 235.59375 31.773438 234.074219 C 31.773438 232.554688 30.6875 232.335938 29.386719 232.335938 L 26.785156 232.335938 Z M 26.785156 235.808594 "
fill-opacity="1" fill-rule="nonzero" />
</g>
<g clip-path="url(#3223358027)">
<path fill="#060165"
d="M 40.230469 241.453125 L 40.230469 232.554688 L 36.761719 232.554688 L 36.761719 231.035156 L 45.652344 231.035156 L 45.652344 232.554688 L 41.964844 232.554688 L 41.964844 241.453125 Z M 40.230469 241.453125 "
fill-opacity="1" fill-rule="nonzero" />
</g>
<g clip-path="url(#f960afe448)">
<path fill="#060165"
d="M 51.292969 231.035156 L 51.292969 236.894531 C 51.292969 238.847656 51.941406 240.152344 54.113281 240.152344 C 56.0625 240.152344 56.933594 238.847656 56.933594 236.894531 L 56.933594 231.035156 L 58.667969 231.035156 L 58.667969 237.113281 C 58.667969 239.71875 57.148438 241.886719 54.113281 241.886719 C 51.074219 241.886719 49.558594 239.933594 49.558594 237.113281 L 49.558594 231.035156 Z M 51.292969 231.035156 "
fill-opacity="1" fill-rule="nonzero" />
</g>
<g clip-path="url(#04b92ea6b9)">
<path fill="#060165"
d="M 68.859375 238.632812 L 64.523438 238.632812 L 63.4375 241.453125 L 61.703125 241.453125 L 65.390625 231.035156 L 67.992188 231.035156 L 71.679688 241.453125 L 69.730469 241.453125 Z M 68.210938 237.113281 L 66.476562 232.335938 L 64.738281 237.113281 Z M 68.210938 237.113281 "
fill-opacity="1" fill-rule="nonzero" />
</g>
<g clip-path="url(#7b4adb9303)">
<path fill="#060165"
d="M 75.800781 241.453125 L 75.800781 231.035156 L 77.753906 231.035156 L 77.753906 239.933594 L 83.394531 239.933594 L 83.394531 241.453125 Z M 75.800781 241.453125 "
fill-opacity="1" fill-rule="nonzero" />
</g>
<g clip-path="url(#53357fd403)">
<path fill="#060165"
d="M 100.3125 238.632812 L 95.972656 238.632812 L 94.886719 241.453125 L 93.152344 241.453125 L 96.839844 231.035156 L 99.441406 231.035156 L 103.128906 241.453125 L 101.179688 241.453125 Z M 99.660156 237.113281 L 97.925781 232.335938 L 96.191406 237.113281 Z M 99.660156 237.113281 "
fill-opacity="1" fill-rule="nonzero" />
</g>
<g clip-path="url(#bcd03dbca6)">
<path fill="#060165"
d="M 108.554688 237.980469 C 108.769531 238.632812 109.421875 240.152344 111.804688 240.152344 C 113.757812 240.152344 114.410156 239.5 114.410156 238.632812 C 114.410156 237.980469 113.542969 237.546875 112.457031 237.328125 L 110.722656 237.113281 C 107.683594 236.460938 107.035156 235.375 107.035156 234.074219 C 107.035156 232.335938 108.554688 231.035156 111.589844 231.035156 C 115.058594 231.035156 116.144531 232.769531 116.359375 234.074219 L 114.625 234.507812 C 114.410156 233.640625 113.324219 232.769531 111.589844 232.769531 C 110.070312 232.769531 108.769531 233.421875 108.769531 234.289062 C 108.769531 235.160156 109.636719 235.59375 110.9375 235.808594 L 112.671875 236.027344 C 115.492188 236.460938 116.144531 237.761719 116.144531 238.847656 C 116.144531 240.800781 114.410156 241.886719 111.589844 241.886719 C 108.117188 241.886719 106.816406 239.933594 106.601562 238.632812 Z M 108.554688 237.980469 "
fill-opacity="1" fill-rule="nonzero" />
</g>
<g clip-path="url(#ff8639fe11)">
<path fill="#060165"
d="M 122 237.980469 C 122.21875 238.632812 122.867188 240.152344 125.253906 240.152344 C 127.207031 240.152344 127.855469 239.5 127.855469 238.632812 C 127.855469 237.980469 126.988281 237.546875 125.90625 237.328125 L 124.167969 237.113281 C 121.132812 236.460938 120.480469 235.375 120.480469 234.074219 C 120.480469 232.335938 122 231.035156 125.035156 231.035156 C 128.507812 231.035156 129.589844 232.769531 129.808594 234.074219 L 128.289062 234.074219 C 128.074219 233.207031 126.988281 232.335938 125.253906 232.335938 C 123.734375 232.335938 122.433594 232.988281 122.433594 233.855469 C 122.433594 234.726562 123.300781 235.160156 124.601562 235.375 L 126.339844 235.59375 C 129.15625 236.027344 129.808594 237.328125 129.808594 238.414062 C 129.808594 240.367188 128.074219 241.453125 125.253906 241.453125 C 121.785156 241.453125 120.480469 239.5 120.265625 238.199219 Z M 122 237.980469 "
fill-opacity="1" fill-rule="nonzero" />
</g>
<g clip-path="url(#3990de54cb)">
<path fill="#060165"
d="M 134.796875 231.035156 L 136.53125 231.035156 L 136.53125 241.671875 L 134.796875 241.671875 Z M 134.796875 231.035156 "
fill-opacity="1" fill-rule="nonzero" />
</g>
<g clip-path="url(#045cbd7804)">
<path fill="#060165"
d="M 143.039062 237.980469 C 143.257812 238.632812 143.90625 240.152344 146.292969 240.152344 C 148.246094 240.152344 148.894531 239.5 148.894531 238.632812 C 148.894531 237.980469 148.027344 237.546875 146.941406 237.328125 L 145.207031 237.113281 C 142.171875 236.460938 141.519531 235.375 141.519531 234.074219 C 141.519531 232.335938 143.039062 231.035156 146.074219 231.035156 C 149.546875 231.035156 150.628906 232.769531 150.847656 234.074219 L 149.113281 234.507812 C 148.894531 233.640625 147.8125 232.769531 146.074219 232.769531 C 144.558594 232.769531 143.257812 233.421875 143.257812 234.289062 C 143.257812 235.160156 144.125 235.59375 145.425781 235.808594 L 147.160156 236.027344 C 149.980469 236.460938 150.628906 237.761719 150.628906 238.847656 C 150.628906 240.800781 148.894531 241.886719 146.074219 241.886719 C 142.605469 241.886719 141.304688 239.933594 141.085938 238.632812 Z M 143.039062 237.980469 "
fill-opacity="1" fill-rule="nonzero" />
</g>
<g clip-path="url(#7de9a85e20)">
<path fill="#060165"
d="M 157.570312 241.453125 L 157.570312 232.554688 L 154.101562 232.554688 L 154.101562 231.035156 L 162.992188 231.035156 L 162.992188 232.554688 L 159.304688 232.554688 L 159.304688 241.453125 Z M 157.570312 241.453125 "
fill-opacity="1" fill-rule="nonzero" />
</g>
<g clip-path="url(#e68f5b3fba)">
<path fill="#060165"
d="M 172.753906 238.632812 L 168.414062 238.632812 L 167.332031 241.453125 L 165.597656 241.453125 L 169.285156 231.035156 L 171.886719 231.035156 L 175.574219 241.453125 L 173.839844 241.453125 Z M 172.320312 237.113281 L 170.585938 232.335938 L 168.847656 237.113281 Z M 172.320312 237.113281 "
fill-opacity="1" fill-rule="nonzero" />
</g>
<g clip-path="url(#65464ab719)">
<path fill="#060165"
d="M 179.042969 241.453125 L 179.042969 231.035156 L 180.777344 231.035156 L 186.636719 238.414062 L 186.636719 231.035156 L 188.371094 231.035156 L 188.371094 241.453125 L 186.851562 241.453125 L 180.996094 234.074219 L 180.996094 241.453125 Z M 179.042969 241.453125 "
fill-opacity="1" fill-rule="nonzero" />
</g>
<g clip-path="url(#b82da71650)">
<path fill="#060165"
d="M 196.179688 241.453125 L 196.179688 232.554688 L 192.492188 232.554688 L 192.492188 231.035156 L 201.382812 231.035156 L 201.382812 232.554688 L 197.914062 232.554688 L 197.914062 241.453125 Z M 196.179688 241.453125 "
fill-opacity="1" fill-rule="nonzero" />
</g>
<g clip-path="url(#fe4eaf6826)">
<path fill="#060165"
d="M 206.371094 237.980469 C 206.589844 238.632812 207.238281 240.152344 209.625 240.152344 C 211.578125 240.152344 212.226562 239.5 212.226562 238.632812 C 212.226562 237.980469 211.359375 237.546875 210.277344 237.328125 L 208.542969 237.113281 C 205.503906 236.460938 204.855469 235.375 204.855469 234.074219 C 204.855469 232.335938 206.371094 231.035156 209.410156 231.035156 C 212.878906 231.035156 213.964844 232.769531 214.179688 234.074219 L 212.445312 234.507812 C 212.226562 233.640625 211.144531 232.769531 209.410156 232.769531 C 207.890625 232.769531 206.589844 233.421875 206.589844 234.289062 C 206.589844 235.160156 207.457031 235.59375 208.757812 235.808594 L 210.492188 236.027344 C 213.3125 236.460938 213.964844 237.761719 213.964844 238.847656 C 213.964844 240.800781 212.230469 241.886719 209.410156 241.886719 C 205.9375 241.886719 204.636719 239.933594 204.421875 238.632812 Z M 206.371094 237.980469 "
fill-opacity="1" fill-rule="nonzero" />
</g>
<g clip-path="url(#79d174fbbf)">
<path fill="#060165"
d="M 225.460938 241.453125 L 225.460938 231.035156 L 233.050781 231.035156 L 233.050781 232.554688 L 227.195312 232.554688 L 227.195312 235.375 L 233.050781 235.375 L 233.050781 236.894531 L 227.195312 236.894531 L 227.195312 241.453125 Z M 225.460938 241.453125 "
fill-opacity="1" fill-rule="nonzero" />
</g>
<g clip-path="url(#b0be87ffda)">
<path fill="#060165"
d="M 242.375 230.816406 C 245.414062 230.816406 248.015625 232.988281 248.015625 236.242188 C 248.015625 239.5 245.414062 241.671875 242.375 241.671875 C 239.339844 241.671875 236.738281 239.5 236.738281 236.242188 C 236.738281 232.988281 239.339844 230.816406 242.375 230.816406 M 246.28125 236.242188 C 246.28125 234.074219 244.546875 232.335938 242.375 232.335938 C 240.207031 232.335938 238.472656 234.074219 238.472656 236.242188 C 238.472656 238.414062 240.207031 240.152344 242.375 240.152344 C 244.546875 240.152344 246.28125 238.414062 246.28125 236.242188 "
fill-opacity="1" fill-rule="nonzero" />
</g>
<g clip-path="url(#444dbef42a)">
<path fill="#060165"
d="M 252.789062 241.453125 L 252.789062 231.035156 L 257.34375 231.035156 C 259.511719 231.035156 261.464844 231.902344 261.464844 234.289062 C 261.464844 235.808594 260.597656 236.679688 259.511719 237.113281 C 260.164062 237.546875 260.378906 238.199219 260.597656 238.847656 C 260.8125 240.152344 261.246094 241.238281 261.464844 241.453125 L 259.511719 241.453125 C 259.292969 241.019531 259.078125 240.367188 258.859375 239.28125 C 258.644531 238.199219 258.210938 237.328125 257.34375 237.328125 L 254.738281 237.328125 L 254.738281 241.453125 Z M 254.523438 235.808594 L 256.910156 235.808594 C 258.210938 235.808594 259.511719 235.59375 259.511719 234.074219 C 259.511719 232.554688 258.425781 232.335938 257.125 232.335938 L 254.523438 232.335938 Z M 254.523438 235.808594 "
fill-opacity="1" fill-rule="nonzero" />
</g>
<g clip-path="url(#314903e6e8)">
<path fill="#060165"
d="M 272.523438 241.453125 L 272.523438 230.816406 L 277.078125 230.816406 C 280.550781 230.816406 282.9375 232.988281 282.9375 236.027344 C 282.9375 239.28125 280.550781 241.238281 277.078125 241.238281 Z M 274.261719 239.933594 L 276.863281 239.933594 C 279.25 239.933594 280.984375 238.632812 280.984375 236.242188 C 280.984375 233.855469 279.25 232.554688 276.863281 232.554688 L 274.261719 232.554688 Z M 274.261719 239.933594 "
fill-opacity="1" fill-rule="nonzero" />
</g>
<g clip-path="url(#2fd70bc44d)">
<path fill="#060165"
d="M 292.480469 230.816406 C 295.515625 230.816406 298.117188 232.988281 298.117188 236.242188 C 298.117188 239.5 295.515625 241.671875 292.480469 241.671875 C 289.441406 241.671875 286.839844 239.5 286.839844 236.242188 C 286.839844 232.988281 289.226562 230.816406 292.480469 230.816406 M 296.382812 236.242188 C 296.382812 234.074219 294.648438 232.335938 292.480469 232.335938 C 290.3125 232.335938 288.574219 234.074219 288.574219 236.242188 C 288.574219 238.414062 290.3125 240.152344 292.480469 240.152344 C 294.648438 240.152344 296.382812 238.414062 296.382812 236.242188 "
fill-opacity="1" fill-rule="nonzero" />
</g>
<g clip-path="url(#032e9be536)">
<path fill="#060165"
d="M 310.480469 238.847656 L 311.78125 239.933594 C 310.699219 241.019531 309.179688 241.671875 307.664062 241.671875 C 304.625 241.671875 302.023438 239.5 302.023438 236.242188 C 302.023438 232.988281 304.625 230.816406 307.664062 230.816406 C 309.398438 230.816406 310.914062 231.46875 311.78125 232.554688 L 310.480469 233.640625 C 309.832031 232.769531 308.746094 232.335938 307.445312 232.335938 C 305.277344 232.335938 303.542969 234.074219 303.542969 236.242188 C 303.542969 238.414062 305.277344 240.152344 307.445312 240.152344 C 308.964844 240.152344 309.832031 239.71875 310.480469 238.847656 "
fill-opacity="1" fill-rule="nonzero" />
</g>
<g clip-path="url(#77ab9ec736)">
<path fill="#060165"
d="M 318.289062 241.453125 L 318.289062 232.554688 L 314.820312 232.554688 L 314.820312 231.035156 L 323.710938 231.035156 L 323.710938 232.554688 L 320.242188 232.554688 L 320.242188 241.453125 Z M 318.289062 241.453125 "
fill-opacity="1" fill-rule="nonzero" />
</g>
<g clip-path="url(#bf7e46828b)">
<path fill="#060165"
d="M 332.605469 230.816406 C 335.640625 230.816406 338.246094 232.988281 338.246094 236.242188 C 338.246094 239.5 335.640625 241.671875 332.605469 241.671875 C 329.570312 241.671875 326.964844 239.5 326.964844 236.242188 C 326.964844 232.988281 329.570312 230.816406 332.605469 230.816406 M 336.507812 236.242188 C 336.507812 234.074219 334.773438 232.335938 332.605469 232.335938 C 330.4375 232.335938 328.699219 234.074219 328.699219 236.242188 C 328.699219 238.414062 330.4375 240.152344 332.605469 240.152344 C 334.773438 240.152344 336.507812 238.414062 336.507812 236.242188 "
fill-opacity="1" fill-rule="nonzero" />
</g>
<g clip-path="url(#b78234ca4f)">
<path fill="#060165"
d="M 343.015625 241.453125 L 343.015625 231.035156 L 347.570312 231.035156 C 349.738281 231.035156 351.691406 231.902344 351.691406 234.289062 C 351.691406 235.808594 350.824219 236.679688 349.738281 237.113281 C 350.390625 237.546875 350.605469 238.199219 350.824219 238.847656 C 351.042969 240.152344 351.476562 241.238281 351.691406 241.453125 L 349.523438 241.453125 C 349.304688 241.019531 349.089844 240.367188 348.871094 239.28125 C 348.65625 238.199219 348.222656 237.328125 347.355469 237.328125 L 344.75 237.328125 L 344.75 241.453125 Z M 344.75 235.808594 L 347.136719 235.808594 C 348.4375 235.808594 349.738281 235.59375 349.738281 234.074219 C 349.738281 232.554688 348.65625 232.335938 347.355469 232.335938 L 344.75 232.335938 Z M 344.75 235.808594 "
fill-opacity="1" fill-rule="nonzero" />
</g>
<g clip-path="url(#2b0e76334b)">
<path fill="#060165"
d="M 357.113281 237.980469 C 357.332031 238.632812 357.980469 240.152344 360.367188 240.152344 C 362.320312 240.152344 362.96875 239.5 362.96875 238.632812 C 362.96875 237.980469 362.101562 237.546875 361.019531 237.328125 L 359.28125 237.113281 C 356.246094 236.460938 355.597656 235.375 355.597656 234.074219 C 355.597656 232.335938 357.113281 231.035156 360.152344 231.035156 C 363.621094 231.035156 364.707031 232.769531 364.921875 234.074219 L 363.1875 234.507812 C 362.96875 233.640625 361.886719 232.769531 360.152344 232.769531 C 358.632812 232.769531 357.332031 233.421875 357.332031 234.289062 C 357.332031 235.160156 358.199219 235.59375 359.5 235.808594 L 361.234375 236.027344 C 364.054688 236.460938 364.707031 237.761719 364.707031 238.847656 C 364.707031 240.800781 362.96875 241.886719 360.152344 241.886719 C 356.679688 241.886719 355.378906 239.933594 355.160156 238.632812 Z M 357.113281 237.980469 "
fill-opacity="1" fill-rule="nonzero" />
</g>
</svg>
</div> </div>
</div> </div>
</body> </body>
</html>
</html>

View File

@ -4,22 +4,15 @@ import { getToken } from '../utils/tokenHelper.js';
import chalk from "chalk"; import chalk from "chalk";
import { sendTokenExpired } from '../ipc/ipcHandler.js'; import { sendTokenExpired } from '../ipc/ipcHandler.js';
import https from 'https'; import https from 'https';
import { getBaseURL } from './baseURL.js';
const TOKEN_KEY = 'ACCESS_TOKEN'; const TOKEN_KEY = 'ACCESS_TOKEN';
const Base_URL = await getBaseURL()
const httpsAgent = new https.Agent({ const httpsAgent = new https.Agent({
rejectUnauthorized: false rejectUnauthorized: false
}); });
console.log('Base_URL$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$' ,Base_URL)
const api: AxiosInstance = axios.create({ const api: AxiosInstance = axios.create({
// baseURL: 'https://portal-sf.docva.com/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 httpsAgent
// timeout: 10000, // timeout: 10000,
}); });
@ -36,7 +29,7 @@ api.interceptors.request.use(
if (token) { if (token) {
config.headers['token'] = token; config.headers['token'] = token;
config.headers['ngrok-skip-browser-warning'] = 'true'; config.headers['ngrok-skip-browser-warning'] = 'true';
} }
return config; return config;
@ -61,7 +54,7 @@ api.interceptors.response.use(
(response: AxiosResponse) => response.data, (response: AxiosResponse) => response.data,
async (error) => { async (error) => {
console.log(error, "Line 46") console.log(error , "Line 46")
const status = error?.response?.status; const status = error?.response?.status;
console.error('[Axios Error]', status); console.error('[Axios Error]', status);
@ -76,4 +69,4 @@ api.interceptors.response.use(
} }
); );
export default api; export default api;

View File

@ -1,7 +0,0 @@
// 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;
};

View File

@ -1,41 +0,0 @@
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("Lin 11 BaseURL CHECK@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" ,baseURL)
try {
const res = await axios.get(`${baseURL}/health`, {
headers: { "ngrok-skip-browser-warning": "true" },
httpsAgent,
timeout: 4000
});
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"
};
}
};

View File

@ -3,9 +3,7 @@ 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";
@ -13,22 +11,6 @@ import { getBaseURL, setBaseURL } from "../api/baseURL.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);
api.defaults.baseURL = `${url}/api/v1/va`;
console.log("Base URL updated to:", api.defaults.baseURL);
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);
}); });
@ -170,30 +152,39 @@ ipcMain.handle("stop-project-tracking", async (event, payload) => {
// tabs opening and closing when and tracking start // tabs opening and closing when and tracking start
ipcMain.handle("tracking-tabs-opening-and-closing", async (event, payload) => { ipcMain.handle("tracking-tabs-opening-and-closing", async (event, payload) => {
try { try {
console.log("🟦 IPC received payload:", payload); console.log("🟦 IPC tracking-tabs-opening-and-closing:", payload);
const response = await api.post("/log/project/track", 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);
}
console.log("🟩 Log track API success:", response); return response;
return {
success: true,
data: response.data,
};
} catch (error) { } catch (error) {
console.error("❌ Log track API error:", error); console.error("Error syncing project tab tracking:", error);
throw error;
return {
success: false,
message: "Something went wrong",
error,
};
} }
}); });
ipcMain.handle("get-project-logs", async (event, { projectID, fromDate, toDate }) => { ipcMain.handle("get-project-logs", async (event, { projectID, fromDate, toDate }) => {
try { try {
console.log("📥 get-project-logs called with:", { projectID, fromDate, toDate }); console.log("📥 get-project-logs called with:", { projectID, fromDate, toDate });
@ -544,11 +535,6 @@ 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");

View File

@ -178,12 +178,6 @@ app.on("ready", async () => {
// HANDLE CLOSE EVENTS // HANDLE CLOSE EVENTS
// ------------------------------------ // ------------------------------------
// handleCloseEvents(win); // handleCloseEvents(win);
mainWindow.on("close", () => {
if(mainWindow !== null) {
mainWindow.webContents.executeJavaScript(`window.__allowClose__ = true`);
}
});
}); });
@ -204,7 +198,6 @@ app.on('browser-window-focus', () => {
// }); // });
// --------------------------- // ---------------------------
// CUSTOM CLOSE BEHAVIOR // CUSTOM CLOSE BEHAVIOR
// --------------------------- // ---------------------------

View File

@ -65,14 +65,9 @@ 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>(

View File

@ -8,7 +8,6 @@ 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
@ -62,16 +61,14 @@ 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" , `${baseURL}/api/v1/va/login`); console.log("Doom 1");
try { try {
console.log("➡️ Calling API..."); console.log("➡️ Calling API...");
const response = await axios.post( const response = await axios.post(
`${baseURL}/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 }
); );

View File

@ -3,7 +3,6 @@ import { exec, execFile } from "child_process";
import fs from "fs"; import fs from "fs";
import os from "os"; import os from "os";
import path from "path"; import path from "path";
import axios from "axios";
// //
// ------------------ HELPERS ------------------ // ------------------ HELPERS ------------------
@ -52,72 +51,20 @@ function getWireGuardInstallerPath(): string {
// OS-specific wg0.conf // 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<string> {
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() const base = isDev()
? path.join(process.cwd(), "resources", "wireguard", "windows") ? path.join(process.cwd(), "resources", "wireguard")
: path.join(process.resourcesPath, "wireguard", "windows"); : path.join(process.resourcesPath, "wireguard");
// Ensure directory exists if (process.platform === "win32") {
fs.mkdirSync(base, { recursive: true }); return path.join(base, "windows", "wg0.conf");
}
// 3️⃣ Create temp / runtime conf file if (process.platform === "darwin") {
// IMPORTANT: name must stay stable (WireGuard service name depends on it) return path.join(base, "macos", "wg0.conf");
const confPath = path.join(base, "wg0.conf"); }
// 4️⃣ Write config to file throw new Error("Unsupported OS");
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 // Detect installed WireGuard.exe on Windows
@ -198,24 +145,16 @@ async function ensureWireGuardInstalled(): Promise<string> {
async function connectWindows(): Promise<string> { async function connectWindows(): Promise<string> {
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 cli = await ensureWireGuardInstalled();
const conf = await getConfigPath(); const conf = getConfigPath();
// Disable GUI popup and silent mode (ignore errors) // 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 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 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 delete "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run" /v WireGuard /f`).catch(() => {});
// Start background service // Start background service
await execPromise(`sc start WireGuardManager`).catch(() => { }); await execPromise(`sc start WireGuardManager`).catch(() => {});
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
execFile(cli, ["/installtunnelservice", conf], (err) => { execFile(cli, ["/installtunnelservice", conf], (err) => {

View File

@ -97,23 +97,22 @@ 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 = () => {
const dispatch = useDispatch<AppDispatch>(); const dispatch = useDispatch<AppDispatch>();
// ✅ Get user directly from Redux // ✅ Get user directly from Redux
const user = useSelector((state: RootState) => state.user.user); const user = useSelector((state: RootState) => state.user.user);
useEffect(() => { useEffect(() => {
dispatch(loadUserFromStorage()); dispatch(loadUserFromStorage());
}, [dispatch]); }, [dispatch]);
console.log("App.tsx → user:", user); console.log("App.tsx → user:", user);
return ( return (
<Router> <Router>
@ -123,9 +122,7 @@ const App: React.FC = () => {
path="/login" path="/login"
element={ element={
<PublicRoute user={user}> <PublicRoute user={user}>
<VpnGuard> <Login />
<Login />
</VpnGuard>
</PublicRoute> </PublicRoute>
} }
/> />
@ -135,22 +132,20 @@ 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 */}

View File

@ -1,14 +1,6 @@
// hooks/useActiveAppTracker.tsx
import { useEffect, useRef, useCallback } from "react"; import { useEffect, useRef, useCallback } from "react";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch } from "react-redux";
import { RootState } from "../store"; import { setActiveWindow, WindowInfo } from "../store/windowSlice";
import { WindowInfo } from "../store/windowSlice";
import {
initializeProjectLogs,
updateWindowLog,
clearProjectLogs,
debugLogsState,
} from "../store/logsSlice";
interface DailyHours { interface DailyHours {
todayTotal: string; todayTotal: string;
@ -24,223 +16,273 @@ export const useActiveAppTracker = (
onLimitExceeded?: () => void onLimitExceeded?: () => void
) => { ) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const { logsByProject } = useSelector((state: RootState) => state.logs);
const lastActiveRef = useRef<WindowInfo | null>(null); const lastActiveRef = useRef<WindowInfo | null>(null);
const lastLogIdRef = useRef<string | null>(null);
const lastTitleRef = useRef<string | null>(null); const lastTitleRef = useRef<string | null>(null);
const lastAppNameRef = useRef<string | null>(null); const intervalIdRef = useRef<number | null>(null);
const intervalIdRef = useRef<ReturnType<typeof setInterval> | null>(null); const finalizeTrackingRef = useRef<(() => Promise<void>) | null>(null);
const limitCheckedRef = useRef(false); const limitCheckedRef = useRef<boolean>(false);
const isStoppingRef = useRef(false); const isStoppingRef = useRef<boolean>(false); // ✅ Flag to prevent new requests
const checkCountRef = useRef(0);
// Initialize logs when tracking starts const canSend = !!(activeProjectLogId && projectId && !isStoppingRef.current);
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})`);
// Reset refs // ✅ Parse time string to minutes
lastActiveRef.current = null; const parseTimeToMinutes = (timeStr: string): number => {
lastTitleRef.current = null; const hourMatch = timeStr.match(/(\d+)h/);
lastAppNameRef.current = null; const minMatch = timeStr.match(/(\d+)m/);
checkCountRef.current = 0;
isStoppingRef.current = false; 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;
} }
}, [isTracking, activeProjectLogId, projectId, projectName, dispatch]);
// Stop tracking intervals return false;
const stopIntervals = useCallback(() => { };
// ✅ 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...");
if (intervalIdRef.current) { if (intervalIdRef.current) {
clearInterval(intervalIdRef.current); clearInterval(intervalIdRef.current);
intervalIdRef.current = null; intervalIdRef.current = null;
console.log("✅ Interval cleared");
} }
}, []); }, []);
// Debug function to check logs state // ✅ Send final PATCH before stopping (called by handleStopTracking)
const debugCurrentLogs = useCallback(() => { const sendFinalPatch = useCallback(async () => {
if (projectId) { console.log("=".repeat(60));
dispatch(debugLogsState({ projectId })); console.log("🛑 FINAL PATCH INITIATED");
} console.log("=".repeat(60));
}, [projectId, dispatch]);
// Finalize and send all logs when stopping tracking // ✅ STEP 1: Set stopping flag IMMEDIATELY
// hooks/useActiveAppTracker.tsx - finalizeAndSendLogs function isStoppingRef.current = true;
const finalizeAndSendLogs = useCallback( console.log("🔒 Stopping flag set — no new requests allowed");
async (vaId: string): Promise<boolean> => {
console.log(" FINALIZING AND SENDING ALL LOGS");
// Set stopping flag // ✅ STEP 2: Stop the interval IMMEDIATELY
isStoppingRef.current = true; stopInterval();
// Stop intervals immediately if (!activeProjectLogId || !projectId) {
stopIntervals(); console.log("⚠️ Skipping final PATCH — no active project log ID");
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 {
// Create a DEEP copy of the logs
const allLogs = JSON.parse(JSON.stringify(projectLogs.logs));
// 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(`📊 Total logs: ${allLogs.length}, Valid logs (≥1000ms): ${validLogs.length}`);
if (validLogs.length === 0) {
console.log("ℹ️ No valid logs to send");
dispatch(clearProjectLogs({ projectId }));
return true;
}
// Prepare payload with all valid logs - send directly as stored
const payload = {
vaId: vaId || "",
projectId,
logId: activeProjectLogId,
logs: validLogs, // Already formatted correctly
};
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 = await 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,
]
);
// Main tracking logic - runs every 5 seconds
useEffect(() => {
if (!isTracking || !projectId || !activeProjectLogId) {
console.log("⏸️ Tracking inactive — stopping interval");
stopIntervals();
return; return;
} }
console.log(`🚀 Starting tracker for: ${projectName}`); // ✅ 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 checkActiveWindow = async () => { const result = await sendActivityLog(
activeProjectLogId,
projectId,
lastTitleRef.current,
lastActiveRef.current.owner.name || "Unknown",
false // PATCH (update existing log)
);
console.log("✅ Final PATCH sent successfully!");
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}`);
}
} catch (error) {
console.error("❌ Final PATCH failed:", error);
throw error;
}
} else {
console.log("ℹ️ No active window to finalize");
}
// ✅ 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
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();
return;
}
console.log(`🟢 Starting active tracker for: ${projectName}`);
const fetchActiveApp = async () => {
// ✅ Double-check stopping flag before each fetch
if (isStoppingRef.current) { if (isStoppingRef.current) {
console.log("🚫 Stopping in progress — skipping fetch");
return; return;
} }
try { try {
const details = (await window.electron.getActiveAppDetails()) as WindowInfo; const details = (await window.electron.getActiveAppDetails()) as WindowInfo;
if (!details || !details.id) return;
if (!details || !details.id || !details.owner?.name) { const title = details.title;
return; const appName = details.owner.name;
} const prevTitle = lastTitleRef.current;
const hasChanged = !prevTitle || prevTitle !== title;
const currentTitle = details.title; if (hasChanged) {
const currentAppName = details.owner.name; // PATCH previous window before creating new log
if (lastLogIdRef.current && prevTitle) {
console.log("🔄 Updating previous window:", prevTitle);
await sendActivityLog(activeProjectLogId!, projectId!, prevTitle, appName, false);
}
checkCountRef.current += 1; // POST new window log
console.log("🆕 New window detected:", title);
await sendActivityLog(activeProjectLogId!, projectId!, title, appName, true);
// Update Redux with current window activity // Update refs and Redux
dispatch( lastActiveRef.current = details;
updateWindowLog({ lastTitleRef.current = title;
projectId, dispatch(setActiveWindow({ window: details, projectId, projectName }));
title: currentTitle, } else {
appName: currentAppName, console.log("⏸️ Same window, skipping");
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) { } catch (error) {
console.error("❌ Failed to check active window:", error); console.error("❌ Failed to fetch active app:", error);
} }
}; };
// Initial check // Initial fetch
checkActiveWindow(); fetchActiveApp();
// Set up interval (every 5 seconds) // Set up interval (every 5 seconds)
intervalIdRef.current = setInterval(checkActiveWindow, 5000); intervalIdRef.current = setInterval(fetchActiveApp, 5000);
// Cleanup // Cleanup on unmount
return () => { return () => {
console.log("🧹 Cleaning up tracker"); console.log("🧹 Cleaning up tracker interval");
stopIntervals(); stopInterval();
}; };
}, [ }, [isTracking, activeProjectLogId, projectId, projectName, dispatch, stopInterval]);
isTracking,
activeProjectLogId,
projectId,
projectName,
stopIntervals,
dispatch,
debugCurrentLogs,
]);
return { return {
finalizeAndSendLogs, finalizeTracking: finalizeTrackingRef.current,
stopIntervals, stopInterval,
setIsStopping: (value: boolean) => { setIsStopping: (value: boolean) => {
isStoppingRef.current = value; isStoppingRef.current = value;
}, console.log(`🔒 Stopping flag manually set to: ${value}`);
debugCurrentLogs, }
}; };
}; };

View File

@ -39,7 +39,6 @@ import { canStartTracking } from "../utils/projectLimits";
import { useTheme } from "../helper/ThemeToggle"; import { useTheme } from "../helper/ThemeToggle";
import LogoutModal from "../modal/LogoutModal"; import LogoutModal from "../modal/LogoutModal";
import { parseTimeStringToSeconds } from "../utils/timeParser"; import { parseTimeStringToSeconds } from "../utils/timeParser";
import { clearAllLogs, clearProjectLogs } from "../store/logsSlice";
interface User { interface User {
id: string; id: string;
@ -101,8 +100,6 @@ const DashboardLayout = () => {
const { projectsData, projectSummary, selectedProject, projectTrackingMap } = const { projectsData, projectSummary, selectedProject, projectTrackingMap } =
useSelector((state: any) => state.projects); useSelector((state: any) => state.projects);
const { logsByProject } = useSelector((state: RootState) => state.logs);
const tracking = selectedProject const tracking = selectedProject
? projectTrackingMap[selectedProject.projectId] ? projectTrackingMap[selectedProject.projectId]
: null; : null;
@ -119,7 +116,7 @@ const DashboardLayout = () => {
{ label: "Schedule", path: "/dashboard/schedule", icon: <ClipboardClock size={18} /> }, { label: "Schedule", path: "/dashboard/schedule", icon: <ClipboardClock size={18} /> },
{ label: "Pays Slips", path: "/dashboard/pays-slips", icon: <CircleDollarSign size={18} /> }, { label: "Pays Slips", path: "/dashboard/pays-slips", icon: <CircleDollarSign size={18} /> },
{ label: "Settings", path: "/dashboard/settings", icon: <SettingsIcon size={18} /> }, { label: "Settings", path: "/dashboard/settings", icon: <SettingsIcon size={18} /> },
// { label: "VPN", path: "/dashboard/vpn", icon: <GlobeLock size={18} /> }, { label: "VPN", path: "/dashboard/vpn", icon: <GlobeLock size={18} /> },
{ label: "Help & Support", path: "/dashboard/help-support", icon: <HelpCircleIcon size={18} /> }, { label: "Help & Support", path: "/dashboard/help-support", icon: <HelpCircleIcon size={18} /> },
]; ];
@ -170,13 +167,11 @@ 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]);
// Initialize useActiveAppTracker and store finalizeTracking in ref // Initialize useActiveAppTracker and store finalizeTracking in ref
const { finalizeAndSendLogs, setIsStopping: setIsStoppingInTracker } = useActiveAppTracker( const { finalizeTracking, setIsStopping: setIsStoppingInTracker } = useActiveAppTracker(
isTracking, isTracking,
activeProjectLogId, activeProjectLogId,
selectedProject?.projectId ?? null, selectedProject?.projectId ?? null,
@ -187,81 +182,95 @@ const DashboardLayout = () => {
// Define handleStopTracking FIRST // Define handleStopTracking FIRST
const handleStopTracking = useCallback(async () => { const handleStopTracking = useCallback(async () => {
if (loadingStop || loadingStart) { if (loadingStop || loadingStart) {
console.log("⏸️ Already processing, skipping...");
return; return;
} }
if (!selectedProject) { if (!selectedProject) {
console.warn("⚠️ No selected project to stop");
return; return;
} }
console.log("=".repeat(70));
console.log("🛑 STOP TRACKING INITIATED"); console.log("🛑 STOP TRACKING INITIATED");
console.log("=".repeat(70));
console.log(`📦 Project: ${selectedProject.projectName}`);
console.log(`⏱️ Elapsed Time: ${elapsedTime}s`);
// Reset alert flag when stopping // Reset alert flag when stopping
limitAlertShownRef.current = false; limitAlertShownRef.current = false;
const currentStopTime = new Date().toISOString(); const currentStopTime = new Date().toISOString();
const projectId = selectedProject.projectId; const projectId = selectedProject.projectId;
const vaId = projectSummary.vaId || "";
// STEP 1: Set stopping flag in tracker // ✅ CRITICAL: Set stopping flag IMMEDIATELY to block all new tab tracking
console.log("🔒 Setting stopping flag...");
if (setIsStoppingInTracker) { if (setIsStoppingInTracker) {
setIsStoppingInTracker(true); setIsStoppingInTracker(true);
} }
// Set loading state
setIsStopping(true); setIsStopping(true);
setLoadingStop(true); setLoadingStop(true);
// STEP 2: Disconnect socket
handleSocketDisconnect(); handleSocketDisconnect();
// STEP 3: Clear local timer // STEP 1: Clear local timer to prevent further client-side updates
console.log("⏹️ STEP 1: Clearing local timer...");
if (timerRef.current) { if (timerRef.current) {
clearInterval(timerRef.current); clearInterval(timerRef.current);
timerRef.current = null; timerRef.current = null;
console.log("✅ Timer cleared");
} }
// STEP 4: Finalize and send logs // STEP 2: Send final PATCH with last active window data
console.log("📤 STEP 4: Finalizing and sending logs..."); console.log("📤 STEP 2: Sending final activity tracking...");
let logsSent = false; if (stopTrackingRef.current) {
if (finalizeAndSendLogs) {
try { try {
logsSent = await finalizeAndSendLogs(vaId); // Pass vaId here await stopTrackingRef.current(); // This calls sendFinalPatch() in the hook
console.log(logsSent ? "✅ Logs sent successfully" : "⚠️ Logs not sent"); console.log("✅ Final activity tracking completed");
} catch (error) { } catch (error) {
console.error("❌ Error sending logs:", error); console.error("❌ Error during final tracking:", error);
} }
} else {
console.log("⚠️ No tracking ref available");
} }
// STEP 5: Send LOGOUT to backend // STEP 3: Send LOGOUT to backend
console.log("🚪 STEP 5: Sending LOGOUT request..."); console.log("🚪 STEP 3: Sending LOGOUT request...");
try { try {
const logoutPayload = { const payload = {
vaId: vaId, vaId: projectSummary.vaId,
logId: activeProjectLogId,
projectId: projectId, projectId: projectId,
type: "LOGOUT", type: "LOGOUT",
time: currentStopTime time: currentStopTime
}; };
await window.electron.stopProjectTracking(logoutPayload); console.log("📡 Logout payload:", payload);
console.log("✅ LOGOUT successful"); const res = await window.electron.stopProjectTracking(payload);
console.log("✅ LOGOUT successful:", res);
} catch (error) { } catch (error) {
console.error("❌ LOGOUT failed:", error); console.error("❌ LOGOUT failed:", error);
} }
// STEP 6: Update Redux tracking state // STEP 4: Update Redux tracking state
dispatch(stopTrackingReducer({ projectId })); console.log("🔄 STEP 4: Updating Redux state...");
dispatch(
stopTrackingReducer({
projectId,
})
);
// STEP 6: Update UI (restore window)
console.log("🪟 STEP 6: Restoring window...");
await window.electron.toggleCloseBtn(true); await window.electron.toggleCloseBtn(true);
await window.electron.resizeWindow(500, 600); await window.electron.resizeWindow(500, 600);
toggleWindowSize.current = true; toggleWindowSize.current = true;
console.log("✅ Window restored");
// Allow React to process the state update // Allow React to process the state update
await new Promise(resolve => setTimeout(resolve, 50)); await new Promise(resolve => setTimeout(resolve, 50));
// STEP 7: Refresh project list // STEP 5: Refresh project list to get updated work hours from server
console.log("📊 STEP 5: Refreshing project list...");
try { try {
const projectListRes = await window.electron.getProjectList(); const projectListRes = await window.electron.getProjectList();
console.log("✅ Project list fetched:", projectListRes); console.log("✅ Project list fetched:", projectListRes);
@ -340,27 +349,20 @@ const DashboardLayout = () => {
console.log("🎉 TRACKING STOPPED SUCCESSFULLY"); console.log("🎉 TRACKING STOPPED SUCCESSFULLY");
console.log("=".repeat(70)); console.log("=".repeat(70));
} }
// STEP 8: Reset logs for this project
if (logsSent && projectId) {
dispatch(clearProjectLogs({ projectId }));
}
console.log("🎉 TRACKING STOPPED SUCCESSFULLY");
}, [ }, [
selectedProject, selectedProject,
projectSummary, projectSummary,
elapsedTime,
dispatch, dispatch,
loadingStop, loadingStop,
loadingStart, loadingStart,
setIsStoppingInTracker, setIsStoppingInTracker
finalizeAndSendLogs,
]); ]);
// Update the ref whenever finalizeTracking changes // Update the ref whenever finalizeTracking changes
// useEffect(() => { useEffect(() => {
// stopTrackingRef.current = finalizeTracking; stopTrackingRef.current = finalizeTracking;
// }, [finalizeTracking]); }, [finalizeTracking]);
const socketRef = useRef<Socket | null>(null); const socketRef = useRef<Socket | null>(null);
const intervalRef = useRef<number | null>(null); const intervalRef = useRef<number | null>(null);
@ -400,7 +402,6 @@ const DashboardLayout = () => {
const handleLogout = async () => { const handleLogout = async () => {
try { try {
dispatch(clearAllLogs());
const res = await window.electron.logout(); const res = await window.electron.logout();
if (res) { if (res) {
dispatch(clearUser()); dispatch(clearUser());
@ -456,11 +457,6 @@ const DashboardLayout = () => {
return; 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 // CRITICAL FIX: Get the current accumulated time BEFORE starting timer
const currentAccumulatedTime = selectedData?.accumulatedTime || parseTimeStringToSeconds(project.totalWorkTime); const currentAccumulatedTime = selectedData?.accumulatedTime || parseTimeStringToSeconds(project.totalWorkTime);
@ -592,7 +588,7 @@ const DashboardLayout = () => {
if (!selectedProject?.projectId) return if (!selectedProject?.projectId) return
console.log(selectedProject?.projectId, "SelectedProject ID") console.log(selectedProject?.projectId, "SelectedProject ID")
socketRef.current = io("https://portal-sf.docva.com", { socketRef.current = io("https://quality-friendly-goldfish.ngrok-free.app", {
transports: ["websocket"], transports: ["websocket"],
secure: true, secure: true,
rejectUnauthorized: false, rejectUnauthorized: false,
@ -728,26 +724,15 @@ 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>
@ -756,7 +741,6 @@ const DashboardLayout = () => {
onClick={() => { onClick={() => {
window.electron.disconnectVPN(); window.electron.disconnectVPN();
}} }}
className="text-red-400"
> >
Disconnect VPN Disconnect VPN
</button> */} </button> */}
@ -929,6 +913,4 @@ const DashboardLayout = () => {
); );
}; };
export default DashboardLayout; export default DashboardLayout;

View File

@ -6,36 +6,6 @@ import { Provider } from "react-redux";
import { store } from "./store"; import { store } from "./store";
import { ToastProvider } from './helper/ToastContext'; import { ToastProvider } from './helper/ToastContext';
declare global {
interface Window {
__allowClose__: boolean;
}
}
// 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.__allowClose__ = false;
window.addEventListener("beforeunload", (e) => {
if (!window.__allowClose__) {
e.returnValue = false;
}
});
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<Provider store={store}> <Provider store={store}>
<ToastProvider> <ToastProvider>

View File

@ -106,7 +106,6 @@ 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)
@ -130,14 +129,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 in redux store // Store data redux store
dispatch(setProjectsDataSlice(res.data)); dispatch(setProjectsDataSlice(res.data));
dispatch( dispatch(
@ -152,67 +151,35 @@ const Dashboard: React.FC = () => {
}) })
); );
// Check if we already have a selected project that's being tracked // Auto-select first project
const currentlySelected = selectedProject; if (res.data.length > 0) {
const currentlyTracking = isTracking; const firstProject = res.data[0];
// Find if the currently selected project still exists in the fresh data
let projectToSelect = null;
if (currentlySelected && currentlyTracking) { console.log("Line 158" , firstProject)
// 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(projectToSelect)); dispatch(setSelectedProjectSlice(firstProject));
// Only initialize tracking if it doesn't exist already const initialElapsed = parseTimeStringToSeconds(firstProject.totalWorkTime);
const trackingExists = projectTrackingMap[projectToSelect.projectId];
if (!trackingExists) { // Initialize tracking data for first project
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)
} }
}; };
@ -279,7 +246,7 @@ const Dashboard: React.FC = () => {
}; };
return ( return (
<main className="flex-1 overflow-hidden no-scrollbar "> <main className="flex-1 overflow-hidden no-scrollbar max-h-[85vh]">
{/* 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>
@ -305,11 +272,6 @@ 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">
@ -368,16 +330,15 @@ 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 */}
@ -401,7 +362,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>

View File

@ -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@docva.com</p> <p className="font-medium text-sm">support@vatechcorp.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">+XXXX XXXXX</p> <p className="font-medium text-sm">+91 98765 43210</p>
</div> </div>
</div> </div>

View File

@ -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("Timesheet Request submitted successfully!", "success"); showToast("PTO Request submitted successfully!", "success");
// Navigate after a small delay // Navigate after a small delay
setTimeout(() => { setTimeout(() => {
@ -680,5 +680,4 @@ const AdjustmentTab: React.FC<AdjustmentTabProps> = ({ projects }) => {
); );
}; };
export default AdjustmentTab; export default AdjustmentTab;

View File

@ -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>

View File

@ -1,7 +1,6 @@
// 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;
@ -20,13 +19,12 @@ 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) {
showToast("New passwords do not match!" , "error"); alert("New passwords do not match!");
return; return;
} }
@ -42,17 +40,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) {
showToast("Password changed successfully!" , "success"); alert("Password changed successfully!");
setOldPassword("") setOldPassword("")
setNewPassword("") setNewPassword("")
setConfirmPassword("") setConfirmPassword("")
onClose(); onClose();
} else { } else {
showToast((response.message || "Failed to change password.") , "error"); alert(response.message || "Failed to change password.");
} }
} catch (error) { } catch (error) {
console.error("Password change error:", error); console.error("Password change error:", error);
showToast("Error changing password." , "error"); alert("Error changing password.");
} finally { } finally {
setLoading(false); setLoading(false);
} }

View File

@ -1,4 +1,3 @@
import { DateTime } from "luxon";
import React from "react"; import React from "react";
interface TabActivity { interface TabActivity {
@ -28,21 +27,6 @@ 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">
@ -62,11 +46,7 @@ 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} — {getTimeDifference( 🕒 Session {idx + 1} — {session.totalSessionTime}
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()} -{" "}
@ -89,10 +69,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>
))} ))}

View File

@ -52,11 +52,10 @@ 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);
@ -64,13 +63,9 @@ 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(() => {
@ -89,6 +84,7 @@ const Timesheets: React.FC = () => {
}; };
const fetchProjects = async () => { const fetchProjects = async () => {
setInitialLoading(true); setInitialLoading(true);
try { try {
@ -124,7 +120,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)
@ -158,6 +154,10 @@ 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" || (isTracking && entry.projectId === selectedProject.projectId))} disabled={formatWorkTime(entry.totalWorkTime) === "0h 0m"}
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" || (isTracking && entry.projectId === selectedProject.projectId)) ? ( {formatWorkTime(entry.totalWorkTime) === "0h 0m" ? (
<EyeOff size={16} /> <EyeOff size={16} />
) : ( ) : (
<Eye size={16} /> <Eye size={16} />

View File

@ -1,201 +1,54 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { Shield, ShieldAlert, RefreshCw, Wifi, Power } 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 [checking, setChecking] = useState(true);
const [vpnConnected, setVpnConnected] = useState(true);
const VPN_API = "http://10.0.0.1:3000"; // const checkVPN = async () => {
const DEFAULT_API = "https://portal-sf.docva.com" // try {
// const res = await window.electron.checkVpnConnection();
const [checking, setChecking] = useState(true); // setVpnConnected(res?.success);
const [vpnConnected, setVpnConnected] = useState(true); // } catch {
const [isRetrying, setIsRetrying] = useState(false); // setVpnConnected(false);
// } finally {
const checkVPN = async () => { // setChecking(false);
try { // }
setChecking(true); // };
const res = await window.electron?.checkRegion();
console.log("VpnGuard", res);
if (res.status === 200) {
setVpnConnected(true);
} else {
setVpnConnected(false);
return;
}
// setVpnConnected(res?.success === true);
} catch (error) {
console.error("VPN check failed:", error);
setVpnConnected(false);
} finally {
setChecking(false);
setIsRetrying(false);
}
};
useEffect(() => {
checkVPN();
}, []);
const connectVPN = async () => {
setChecking(true);
try {
const res = await window.electron.connectVPN();
console.log("connectVPN result → ", res);
// Only proceed if VPN actually connected
if (res === "Windows VPN connected silently") {
const vpnRes = await window.electron.updateBaseURL(VPN_API);
console.log("vpnRes", vpnRes);
if (vpnRes) {
// Run VPN check again AFTER URL changes
await checkVPN();
} else {
console.warn("Base URL updated but health check failed");
}
}
} catch (error) {
console.log(error);
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);
}
};
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 (
<div className="min-h-screen flex flex-col justify-center items-center bg-base-200">
<div className="relative">
<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>
);
}
// VPN not connected state
if (!vpnConnected) {
return (
<div className="min-h-screen flex flex-col justify-center items-center bg-base-200 p-4">
<div className="max-w-md w-full">
<div className="card bg-base-100 shadow-2xl">
<div className="card-body items-center text-center">
{/* Icon */}
<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>
<h2 className="card-title text-3xl mb-3">VPN Required</h2>
<p className="mb-6 opacity-80">
Please connect to your company VPN to access this application.
</p>
{/* Connect VPN */}
<button
className="btn btn-primary btn-wide gap-2"
onClick={connectVPN}
disabled={isRetrying || vpnConnected} // Disable if retrying OR already connected
>
{isRetrying ? (
<>
<span className="loading loading-spinner loading-sm"></span>
Checking Connection...
</>
) : (
<>
<RefreshCw className="w-5 h-5" />
Connect VPN
</>
)}
</button>
{/* Disconnect VPN (Disabled when NOT connected) */}
{/* <button
className="btn btn-error btn-wide gap-2 mt-3"
onClick={disconnectVPN}
// disabled={!vpnConnected || checking} // Disable if not connected
>
<Power className="w-4 h-4" />
Disconnect VPN
</button> */}
<div className="divider"></div>
<p className="text-xs opacity-60">
Need help? Contact IT support at{" "}
<a href="mailto:support@docva.com" className="link link-primary">
support@docva.com
</a>
</p>
</div>
</div>
</div>
</div>
);
}
useEffect(() => {
// checkVPN();
}, []);
if (checking) {
return ( return (
<> <div className="min-h-screen flex justify-center items-center">
{children} <span className="loading loading-spinner loading-lg"></span>
</> <span className="ml-3">Checking VPN...</span>
</div>
); );
}
if (!vpnConnected) {
return (
<div className="min-h-screen flex justify-center items-center text-center">
<div className="card shadow-xl bg-base-100 p-6">
<h2 className="text-xl font-bold text-red-600">VPN Required</h2>
<p className="mt-2">Please connect to your company VPN to continue.</p>
<button className="btn btn-primary mt-4"
onClick={() => window.location.reload()}>
Retry
</button>
</div>
</div>
);
}
return <>{children}</>;
}; };
export default VpnGuard; export default VpnGuard;

View File

@ -4,7 +4,6 @@ import windowReducer from "./windowSlice";
import projectsReducer from "./projectSlice"; import projectsReducer from "./projectSlice";
import userReducer from "./userSlice"; import userReducer from "./userSlice";
import profileReducer from "./profileSlice"; import profileReducer from "./profileSlice";
import logsReducer from './logsSlice';
export const store = configureStore({ export const store = configureStore({
reducer: { reducer: {
@ -12,7 +11,6 @@ export const store = configureStore({
projects: projectsReducer, projects: projectsReducer,
user: userReducer, user: userReducer,
profile: profileReducer, profile: profileReducer,
logs: logsReducer,
}, },
}); });

View File

@ -1,240 +0,0 @@
// 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 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
}
interface LogsState {
logsByProject: Record<string, ProjectLogs>;
}
const initialState: LogsState = {
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 = 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,
};
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}`);
},
},
});
export const {
initializeProjectLogs,
updateWindowLog,
clearProjectLogs,
clearAllLogs,
debugLogsState,
fixAllTimestamps,
} = logsSlice.actions;
export default logsSlice.reducer;

43
types.d.ts vendored
View File

@ -133,33 +133,6 @@ interface PayslipResponse {
} }
interface RegionResponse {
success: boolean;
status: number;
data?: any;
retry?: boolean;
message?: string;
}
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 { interface Window {
electron: { electron: {
login: (credentials: { email: string; password: string }) => Promise<any>; login: (credentials: { email: string; password: string }) => Promise<any>;
@ -185,12 +158,18 @@ interface Window {
}) => Promise<any>; }) => Promise<any>;
stopProjectTracking: (data: { stopProjectTracking: (data: {
vaId: string; vaId: string;
logId: string,
projectId: string; projectId: string;
type: string; type: string;
time: string time: string
}) => Promise<any>; }) => Promise<any>;
trackingTabsOpeningClosing: (data: TrackingPayload) => Promise<any>; trackingTabsOpeningClosing: (data: {
id?: string;
logId: string;
projectId: string;
title: string;
appName: string;
}) => Promise<any>;
getProjectLogs: ( getProjectLogs: (
projectId: string, projectId: string,
fromDate?: string, fromDate?: string,
@ -263,14 +242,8 @@ 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;
}>;
}; };
} }

View File

@ -1,19 +0,0 @@
// 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==