Skip to content

Commit 11a798b

Browse files
feat: add UI notifications and progress bar for auto-updates
1 parent 6eadbc7 commit 11a798b

4 files changed

Lines changed: 137 additions & 5 deletions

File tree

src/main/index.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -650,17 +650,31 @@ ipcMain.handle('delete-vercel-project', async (_event, params: { projectId: stri
650650
})
651651

652652
// Auto-Updater Configuration
653-
autoUpdater.on('update-available', () => {
654-
console.log('[Updater] Update available')
653+
autoUpdater.on('checking-for-update', () => {
654+
mainWindow?.webContents.send('update-status', 'checking')
655655
})
656656

657-
autoUpdater.on('update-downloaded', () => {
658-
console.log('[Updater] Update downloaded; will install on quit')
659-
autoUpdater.quitAndInstall()
657+
autoUpdater.on('update-available', (info) => {
658+
console.log('[Updater] Update available:', info.version)
659+
mainWindow?.webContents.send('update-status', 'available', info.version)
660+
})
661+
662+
autoUpdater.on('update-not-available', () => {
663+
mainWindow?.webContents.send('update-status', 'up-to-date')
664+
})
665+
666+
autoUpdater.on('download-progress', (progressObj) => {
667+
mainWindow?.webContents.send('update-progress', Math.round(progressObj.percent))
668+
})
669+
670+
autoUpdater.on('update-downloaded', (info) => {
671+
console.log('[Updater] Update downloaded:', info.version)
672+
mainWindow?.webContents.send('update-status', 'downloaded', info.version)
660673
})
661674

662675
autoUpdater.on('error', (err) => {
663676
console.error('[Updater] Error:', err)
677+
mainWindow?.webContents.send('update-status', 'error', err.message)
664678
})
665679

666680
app.whenReady().then(() => {

src/preload/index.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ export interface ElectronAPI {
4646
deployToVercel: (params: { databaseUrl: string, adminToken?: string, projectId: string, projectName: string }) => Promise<{ success: boolean; url?: string; error?: string }>
4747
deleteVercelProject: (params: { projectId: string, projectName: string }) => Promise<{ success: boolean; error?: string }>
4848
onDeployOutput: (callback: (data: string) => void) => () => void
49+
// Updates
50+
onUpdateStatus: (callback: (status: string, version?: string) => void) => () => void
51+
onUpdateProgress: (callback: (percent: number) => void) => () => void
4952
// Platform info
5053
platform: string
5154
}
@@ -78,6 +81,16 @@ const electronAPI: ElectronAPI = {
7881
ipcRenderer.on('deploy-output', subscription)
7982
return () => ipcRenderer.removeListener('deploy-output', subscription)
8083
},
84+
onUpdateStatus: (callback) => {
85+
const subscription = (_event: any, status: string, version?: string) => callback(status, version)
86+
ipcRenderer.on('update-status', subscription)
87+
return () => ipcRenderer.removeListener('update-status', subscription)
88+
},
89+
onUpdateProgress: (callback) => {
90+
const subscription = (_event: any, percent: number) => callback(percent)
91+
ipcRenderer.on('update-progress', subscription)
92+
return () => ipcRenderer.removeListener('update-progress', subscription)
93+
},
8194
platform: process.platform
8295
}
8396

src/renderer/src/App.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { DatabaseSettingsDialog } from './components/DatabaseSettingsDialog'
1414
import { RbacSettingsDialog } from './components/RbacSettingsDialog'
1515
import { DeployProxyDialog } from './components/DeployProxyDialog'
1616
import { GeneralSettingsDialog } from './components/GeneralSettingsDialog'
17+
import { UpdaterNotifier } from './components/UpdaterNotifier'
1718

1819
export function App() {
1920
const {
@@ -107,6 +108,9 @@ export function App() {
107108
{showRbacSettings && <RbacSettingsDialog />}
108109
{showDeploySettings && <DeployProxyDialog />}
109110
{showGeneralSettings && <GeneralSettingsDialog />}
111+
112+
{/* Auto-Updater Visual Layer */}
113+
<UpdaterNotifier />
110114
</div>
111115
)
112116
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { useState, useEffect } from 'react'
2+
3+
export function UpdaterNotifier() {
4+
const [status, setStatus] = useState<string | null>(null)
5+
const [version, setVersion] = useState<string | null>(null)
6+
const [progress, setProgress] = useState<number>(0)
7+
const [visible, setVisible] = useState(false)
8+
9+
useEffect(() => {
10+
const removeStatus = (window as any).electronAPI.onUpdateStatus((newStatus: string, newVersion?: string) => {
11+
console.log('[UpdaterNotifier] status:', newStatus, newVersion)
12+
setStatus(newStatus)
13+
if (newVersion) setVersion(newVersion)
14+
15+
if (['available', 'downloading', 'downloaded', 'error'].includes(newStatus)) {
16+
setVisible(true)
17+
} else if (newStatus === 'up-to-date') {
18+
// Keep hidden
19+
}
20+
})
21+
22+
const removeProgress = (window as any).electronAPI.onUpdateProgress((percent: number) => {
23+
setProgress(percent)
24+
if (percent > 0) setStatus('downloading')
25+
})
26+
27+
return () => {
28+
removeStatus()
29+
removeProgress()
30+
}
31+
}, [])
32+
33+
if (!visible) return null
34+
35+
return (
36+
<div className="fixed bottom-6 right-6 z-[200] animate-in slide-in-from-right-10 duration-500">
37+
<div className="bg-[#111] border border-white/10 rounded-2xl p-5 shadow-2xl flex flex-col gap-4 w-[320px] backdrop-blur-xl">
38+
<div className="flex items-center gap-3">
39+
<div className="bg-white/5 p-2.5 rounded-xl border border-white/10">
40+
{status === 'downloaded' ? (
41+
<svg className="w-5 h-5 text-emerald-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2.5">
42+
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
43+
</svg>
44+
) : status === 'error' ? (
45+
<svg className="w-5 h-5 text-red-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2.5">
46+
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
47+
</svg>
48+
) : (
49+
<div className="w-5 h-5 border-2 border-white/20 border-t-white rounded-full animate-spin" />
50+
)}
51+
</div>
52+
<div>
53+
<h4 className="text-[14px] font-bold text-white mb-0.5">
54+
{status === 'available' && 'Update Available'}
55+
{status === 'downloading' && 'Downloading Update'}
56+
{status === 'downloaded' && 'Update Ready'}
57+
{status === 'error' && 'Update Failed'}
58+
</h4>
59+
<p className="text-[12px] text-neutral-400 font-medium">
60+
{status === 'available' && `Version ${version} is ready`}
61+
{status === 'downloading' && `Fetching software overhaul...`}
62+
{status === 'downloaded' && `Installing v${version} now!`}
63+
{status === 'error' && 'Check connection'}
64+
</p>
65+
</div>
66+
</div>
67+
68+
{(status === 'downloading' || status === 'available') && (
69+
<div className="space-y-2">
70+
<div className="h-1.5 w-100 bg-white/5 rounded-full overflow-hidden">
71+
<div
72+
className="h-full bg-white transition-all duration-300 ease-out"
73+
style={{ width: `${progress}%` }}
74+
/>
75+
</div>
76+
<div className="flex justify-between items-center text-[10px] uppercase tracking-widest font-bold text-neutral-500">
77+
<span>{progress}%</span>
78+
<span>Secure Layer</span>
79+
</div>
80+
</div>
81+
)}
82+
83+
{status === 'downloaded' && (
84+
<div className="text-[10px] uppercase tracking-widest font-extrabold text-emerald-500/80 flex items-center gap-2">
85+
<span className="flex h-1.5 w-1.5 rounded-full bg-emerald-500 animate-pulse" />
86+
Restarting to apply v{version}
87+
</div>
88+
)}
89+
90+
<button
91+
onClick={() => setVisible(false)}
92+
className="absolute top-3 right-3 text-neutral-600 hover:text-white transition-colors"
93+
>
94+
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
95+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
96+
</svg>
97+
</button>
98+
</div>
99+
</div>
100+
)
101+
}

0 commit comments

Comments
 (0)