diff --git a/README.md b/README.md index b81b491..e4e80ad 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,52 @@ -
-GHBanner -
+# Git Watcher Pro -# Run and deploy your AI Studio app +Git Watcher Pro is a lightweight, proactive Git repository monitor for Windows. It sits in your system tray and monitors your local repositories for changes, keeping you synced with your remote servers. -This contains everything you need to run your app locally. +![Dashboard](docs/images/dashboard.png) -View your app in AI Studio: https://ai.studio/apps/drive/1NNtM1N_9cpRVQ7KoDZrJoSs90I9ZMeCA +## Features -## Run Locally +- **Real-time Monitoring:** Automatically checks for changes in your Git repositories. +- **Smart Status:** Shows if you are ahead, behind, or have dirty files. +- **Auto-Pull:** Optionally configure repositories to automatically pull changes when behind. +- **System Tray Integration:** Runs quietly in the background; minimize to tray to keep your taskbar clean. +- **Smart Auth:** Easily connect to private GitHub repositories using the clipboard-based Smart Connect feature. +- **Portable:** No installation required. Just run the executable. +- **Startup:** Can be configured to launch automatically with Windows. -**Prerequisites:** Node.js +![Settings](docs/images/settings.png) +## Getting Started -1. Install dependencies: - `npm install` -2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key -3. Run the app: - `npm run dev` +### Prerequisites + +- Windows 10/11 +- Git installed and available in PATH. + +### Installation + +1. Download the latest release (Portable .exe). +2. Run `Git Watcher Pro.exe`. + +### Development + +1. **Install dependencies:** + ```bash + npm install + ``` + +2. **Run in development mode:** + ```bash + npm run dev + ``` + *Note: In a headless environment, use `npm run preview` to see the UI.* + +3. **Build:** + ```bash + npm run build + ``` + This produces an installer and a portable executable in `dist/`. + +## License + +MIT diff --git a/docs/images/dashboard.png b/docs/images/dashboard.png new file mode 100644 index 0000000..b2e9e6b Binary files /dev/null and b/docs/images/dashboard.png differ diff --git a/docs/images/settings.png b/docs/images/settings.png new file mode 100644 index 0000000..9927964 Binary files /dev/null and b/docs/images/settings.png differ diff --git a/electron/assets/icon.png b/electron/assets/icon.png index c190e85..a48fa79 100644 Binary files a/electron/assets/icon.png and b/electron/assets/icon.png differ diff --git a/electron/main.ts b/electron/main.ts index 815cec9..f69d36a 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -22,7 +22,7 @@ const PRELOAD_PATH = join(ROOT_PATH, 'dist-electron/preload.cjs') const store = new Store({ defaults: { repos: [], - settings: { checkInterval: 60, githubToken: '', launchAtStartup: true } + settings: { checkInterval: 60, githubToken: '', launchAtStartup: true, startMinimized: false } } }) @@ -163,10 +163,12 @@ function startClipboardWatcher() { // --- App Lifecycle --- function createWindow(): void { + const startMinimized = store.get('settings.startMinimized') as boolean; + mainWindow = new BrowserWindow({ width: 950, height: 700, - show: false, + show: false, // Always start hidden, we show it later if not startMinimized frame: false, backgroundColor: '#1e1e1e', autoHideMenuBar: true, @@ -179,7 +181,9 @@ function createWindow(): void { }) mainWindow.on('ready-to-show', () => { - mainWindow?.show() + if (!startMinimized) { + mainWindow?.show() + } }) mainWindow.on('close', (event) => { @@ -236,6 +240,13 @@ app.whenReady().then(() => { app.setAppUserModelId('com.gitwatcher.app') } + // Ensure startup setting is applied on launch + const launchAtStartup = store.get('settings.launchAtStartup') as boolean; + app.setLoginItemSettings({ + openAtLogin: launchAtStartup, + path: app.getPath('exe') + }); + createWindow() createTray() @@ -285,6 +296,15 @@ app.whenReady().then(() => { if (checkIntervalTimer) clearInterval(checkIntervalTimer); checkIntervalTimer = setInterval(checkRepos, newSettings.checkInterval * 1000); } + + // Handle Launch at Startup + if (newSettings.launchAtStartup !== undefined) { + app.setLoginItemSettings({ + openAtLogin: newSettings.launchAtStartup, + path: app.getPath('exe') + }); + } + return true; }); diff --git a/package-lock.json b/package-lock.json index 2dd2033..22282f3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,10 @@ "name": "git-watcher-pro", "version": "1.0.0", "dependencies": { + "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-icons": "^1.3.2", + "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-slider": "^1.3.6", "@radix-ui/react-slot": "^1.0.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", @@ -1302,6 +1306,92 @@ "node": ">=14" } }, + "node_modules/@radix-ui/number": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", + "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", + "license": "MIT" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-checkbox": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", + "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-compose-refs": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", @@ -1317,6 +1407,189 @@ } } }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-icons": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.2.tgz", + "integrity": "sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==", + "license": "MIT", + "peerDependencies": { + "react": "^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.8.tgz", + "integrity": "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slider": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.3.6.tgz", + "integrity": "sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-slot": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", @@ -1335,6 +1608,91 @@ } } }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.27", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", @@ -1850,8 +2208,9 @@ "version": "18.3.7", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", - "dev": true, + "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^18.0.0" } @@ -5780,6 +6139,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" diff --git a/package.json b/package.json index a120c87..d166556 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,10 @@ "appId": "com.gitwatcher.app", "productName": "Git Watcher Pro", "win": { - "target": "nsis", + "target": [ + "nsis", + "portable" + ], "sign": null, "forceCodeSigning": false, "verifyUpdateCodeSignature": false @@ -27,6 +30,10 @@ } }, "dependencies": { + "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-icons": "^1.3.2", + "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-slider": "^1.3.6", "@radix-ui/react-slot": "^1.0.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", diff --git a/src/Settings.tsx b/src/Settings.tsx index ce5a5f2..1bd0201 100644 --- a/src/Settings.tsx +++ b/src/Settings.tsx @@ -1,9 +1,17 @@ import { useState, useEffect } from 'react'; +import { Button } from './components/ui/Button'; +import { Card, CardContent } from './components/ui/Card'; +import { Input } from './components/ui/Input'; +import { Label } from './components/ui/Label'; +import { Checkbox } from './components/ui/Checkbox'; +import { Slider } from './components/ui/Slider'; +import { Loader2, Check, Wand2, Monitor, Github, Settings as SettingsIcon, Clock, Power } from 'lucide-react'; export const Settings = () => { const [githubToken, setGithubToken] = useState(''); - const [checkInterval, setCheckInterval] = useState(60); + const [checkInterval, setCheckInterval] = useState(60); // In seconds from DB const [launchAtStartup, setLaunchAtStartup] = useState(true); + const [startMinimized, setStartMinimized] = useState(false); const [isConnecting, setIsConnecting] = useState(false); const [isSmartWaiting, setIsSmartWaiting] = useState(false); @@ -18,6 +26,7 @@ export const Settings = () => { setCheckInterval(settings.checkInterval || 60); setGithubToken(settings.githubToken || ''); setLaunchAtStartup(settings.launchAtStartup !== false); + setStartMinimized(settings.startMinimized || false); } }); @@ -35,9 +44,10 @@ export const Settings = () => { const handleSave = async (override: any = {}) => { setSaveStatus('Saving...'); const settings = { - checkInterval: parseInt(checkInterval.toString()), + checkInterval, // Saved in seconds githubToken, launchAtStartup, + startMinimized, ...override }; @@ -48,6 +58,19 @@ export const Settings = () => { } }; + const handleIntervalChange = (val: number[]) => { + // Slider returns array, usually [value] + // We assume slider works in Minutes (1-60). Convert to Seconds for state. + const minutes = val[0]; + setCheckInterval(minutes * 60); + }; + + const handleIntervalCommit = (val: number[]) => { + // Trigger save on release + const minutes = val[0]; + handleSave({ checkInterval: minutes * 60 }); + }; + const handleConnect = () => { if (!githubToken) return; setIsConnecting(true); @@ -71,125 +94,173 @@ export const Settings = () => { window.electronAPI?.startSmartAuth(); }; - return ( -
-

Settings

- -
-

General

- -
- - setCheckInterval(parseInt(e.target.value))} - onBlur={() => handleSave()} - style={{ background: '#2d2d30', border: '1px solid #444', color: 'white', padding: '6px', borderRadius: '4px', width: '80px' }} - /> -
- -
- -
-
+ // Computed values for display + const currentIntervalMinutes = Math.max(1, Math.round(checkInterval / 60)); - {/* GitHub Authentication Section */} -
-

GitHub Authentication

- -
-
-
-

- Connect your GitHub account to access private repositories. -

- - {/* Smart Auth Button */} - -
- - {isSmartWaiting && ( -
- Browser opened. Generate the token and just copy it to your clipboard. We'll detect it automatically! -
- )} - - - -
-
- setGithubToken(e.target.value)} - style={{ flex: 1, background: '#1e1e1e', border: connectionStatus === 'error' ? '1px solid #f14c4c' : '1px solid #444', color: 'white', padding: '8px', borderRadius: '4px', fontFamily: 'monospace' }} - /> - -
-
- -
- - Manual token generation ↗ - - Required scopes: repo, read:user -
-
-
+ return ( +
+
+
+

Settings

+

Manage your preferences and integrations.

+
+
+ {saveStatus && {saveStatus}} +
-
- - {saveStatus && {saveStatus}} +
+ {/* GitHub Section - High Priority */} +
+
+ +

GitHub Integration

+
+ + + +
+ {/* Manual Input Area */} +
+
+ +

+ Required for accessing private repositories. +

+
+ +
+ setGithubToken(e.target.value)} + className={`font-mono h-8 text-xs ${connectionStatus === 'error' ? 'border-destructive focus-visible:ring-destructive' : ''}`} + /> + +
+
+ + {/* Smart Connect Area */} +
+
+

Smart Connect

+
+ + +
+
+
+
+
+ + {/* General Settings */} +
+
+ +

Application Preferences

+
+ + + + {/* Row 1: Check Interval (Slider) */} +
+
+ +

+ How often to fetch updates. +

+
+ +
+ +
+ {currentIntervalMinutes} min +
+
+
+ + {/* Row 2: Startup */} +
+
+ +

+ Start automatically on login. +

+
+ { + setLaunchAtStartup(checked === true); + handleSave({ launchAtStartup: checked === true }); + }} + /> +
+ + {/* Row 3: Minimize */} +
+
+ +

+ Launch directly to system tray. +

+
+ { + setStartMinimized(checked === true); + handleSave({ startMinimized: checked === true }); + }} + /> +
+
+
+
); -}; \ No newline at end of file +}; diff --git a/src/components/ui/Checkbox.tsx b/src/components/ui/Checkbox.tsx new file mode 100644 index 0000000..209a629 --- /dev/null +++ b/src/components/ui/Checkbox.tsx @@ -0,0 +1,27 @@ +import * as React from "react" +import * as CheckboxPrimitive from "@radix-ui/react-checkbox" +import { CheckIcon } from "@radix-ui/react-icons" +import { cn } from "@/lib/utils" + +const Checkbox = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + +)) +Checkbox.displayName = CheckboxPrimitive.Root.displayName + +export { Checkbox } diff --git a/src/components/ui/Input.tsx b/src/components/ui/Input.tsx new file mode 100644 index 0000000..62b3b66 --- /dev/null +++ b/src/components/ui/Input.tsx @@ -0,0 +1,24 @@ +import * as React from "react" +import { cn } from "@/lib/utils" + +export interface InputProps + extends React.InputHTMLAttributes {} + +const Input = React.forwardRef( + ({ className, type, ...props }, ref) => { + return ( + + ) + } +) +Input.displayName = "Input" + +export { Input } diff --git a/src/components/ui/Label.tsx b/src/components/ui/Label.tsx new file mode 100644 index 0000000..e0d8e04 --- /dev/null +++ b/src/components/ui/Label.tsx @@ -0,0 +1,23 @@ +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" +import { cva, type VariantProps } from "class-variance-authority" +import { cn } from "@/lib/utils" + +const labelVariants = cva( + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" +) + +const Label = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, ...props }, ref) => ( + +)) +Label.displayName = LabelPrimitive.Root.displayName + +export { Label } diff --git a/src/components/ui/Slider.tsx b/src/components/ui/Slider.tsx new file mode 100644 index 0000000..2751eba --- /dev/null +++ b/src/components/ui/Slider.tsx @@ -0,0 +1,30 @@ +import * as React from "react" +import * as SliderPrimitive from "@radix-ui/react-slider" + +import { cn } from "@/lib/utils" + +const Slider = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + + +)) +Slider.displayName = SliderPrimitive.Root.displayName + +export { Slider } diff --git a/verification/dashboard_preview.png b/verification/dashboard_preview.png deleted file mode 100644 index e020fbf..0000000 Binary files a/verification/dashboard_preview.png and /dev/null differ diff --git a/verification/verify_changes.py b/verification/verify_changes.py deleted file mode 100644 index 6c2206a..0000000 --- a/verification/verify_changes.py +++ /dev/null @@ -1,21 +0,0 @@ -from playwright.sync_api import sync_playwright - -def verify_changes(): - with sync_playwright() as p: - browser = p.chromium.launch(headless=True) - page = browser.new_page() - try: - page.goto("http://localhost:4173") - page.wait_for_load_state("networkidle") - - # Take a screenshot of the Dashboard - page.screenshot(path="verification/dashboard_preview.png") - print("Screenshot taken: verification/dashboard_preview.png") - - except Exception as e: - print(f"Error: {e}") - finally: - browser.close() - -if __name__ == "__main__": - verify_changes()