1- import { Button } from "@/components/ui/button" ;
2- import { ToolTipLabel } from "@/components/ui/tooltip" ;
3- import {
4- type EngineInstance ,
5- useEngineGetDeployment ,
6- useEngineSystemHealth ,
7- useEngineUpdateDeployment ,
8- } from "@3rdweb-sdk/react/hooks/useEngine" ;
9- import { CircleArrowDownIcon , CloudDownloadIcon } from "lucide-react" ;
10- import { useState } from "react" ;
11-
121import { Spinner } from "@/components/ui/Spinner/Spinner" ;
2+ import { Alert , AlertDescription , AlertTitle } from "@/components/ui/alert" ;
3+ import { Badge } from "@/components/ui/badge" ;
4+ import { Button } from "@/components/ui/button" ;
135import {
146 Dialog ,
157 DialogContent ,
@@ -18,7 +10,28 @@ import {
1810 DialogHeader ,
1911 DialogTitle ,
2012} from "@/components/ui/dialog" ;
13+ import {
14+ Select ,
15+ SelectContent ,
16+ SelectGroup ,
17+ SelectItem ,
18+ SelectTrigger ,
19+ SelectValue ,
20+ } from "@/components/ui/select" ;
21+ import { ToolTipLabel } from "@/components/ui/tooltip" ;
2122import { TrackedLinkTW } from "@/components/ui/tracked-link" ;
23+ import {
24+ type EngineInstance ,
25+ useEngineGetDeploymentPublicConfiguration ,
26+ useEngineSystemHealth ,
27+ useEngineUpdateDeployment ,
28+ } from "@3rdweb-sdk/react/hooks/useEngine" ;
29+ import {
30+ CircleArrowDownIcon ,
31+ CloudDownloadIcon ,
32+ TriangleAlertIcon ,
33+ } from "lucide-react" ;
34+ import { useState } from "react" ;
2235import { toast } from "sonner" ;
2336import invariant from "tiny-invariant" ;
2437
@@ -27,28 +40,39 @@ export const EngineVersionBadge = ({
2740} : {
2841 instance : EngineInstance ;
2942} ) => {
43+ const teamId = "DEBUG - UNIMPLEMENTED" ;
44+
3045 const healthQuery = useEngineSystemHealth ( instance . url ) ;
31- const latestVersionQuery = useEngineGetDeployment ( ) ;
46+ const publicConfigurationQuery = useEngineGetDeploymentPublicConfiguration ( {
47+ teamId,
48+ } ) ;
3249 const [ isModalOpen , setModalOpen ] = useState ( false ) ;
3350
34- const currentVersion = healthQuery . data ?. engineVersion ?? "..." ;
35- const latestVersion = latestVersionQuery . data ;
36- const isStale = latestVersion && currentVersion !== latestVersion ;
51+ if ( ! healthQuery . data || ! publicConfigurationQuery . data ) {
52+ return null ;
53+ }
54+
55+ const currentVersion = healthQuery . data . engineVersion ?? "N/A" ;
56+ const hasNewerVersion =
57+ publicConfigurationQuery . data . serverVersions . latest !== currentVersion ;
3758
38- if ( ! isStale ) {
59+ // Hide the change version modal unless owner.
60+ if ( ! instance . deploymentId ) {
3961 return (
40- < ToolTipLabel label = "Latest Version" >
41- < Button variant = "outline" asChild className = "hover:bg-transparent" >
42- < div > { currentVersion } </ div >
43- </ Button >
44- </ ToolTipLabel >
62+ < Button variant = "outline" asChild className = "hover:bg-transparent" >
63+ < div > { currentVersion } </ div >
64+ </ Button >
4565 ) ;
4666 }
4767
4868 return (
4969 < >
5070 < ToolTipLabel
51- label = "New version is available"
71+ label = {
72+ hasNewerVersion
73+ ? "An update is available"
74+ : "You are on the latest version"
75+ }
5276 leftIcon = {
5377 < CircleArrowDownIcon className = "size-4 text-link-foreground" />
5478 }
@@ -60,38 +84,41 @@ export const EngineVersionBadge = ({
6084 >
6185 { currentVersion }
6286
63- { /* Notification Dot */ }
64- < span className = "-top-1 -right-1 absolute" >
65- < PulseDot />
66- </ span >
87+ { /* Notification dot if an update is available */ }
88+ { hasNewerVersion && (
89+ < span className = "-top-1 -right-1 absolute" >
90+ < PulseDot />
91+ </ span >
92+ ) }
6793 </ Button >
6894 </ ToolTipLabel >
6995
70- { latestVersion && (
71- < UpdateVersionModal
72- open = { isModalOpen }
73- onOpenChange = { setModalOpen }
74- latestVersion = { latestVersion }
75- instance = { instance }
76- />
77- ) }
96+ < ChangeVersionModal
97+ open = { isModalOpen }
98+ onOpenChange = { setModalOpen }
99+ instance = { instance }
100+ currentVersion = { currentVersion }
101+ serverVersions = { publicConfigurationQuery . data . serverVersions }
102+ />
78103 </ >
79104 ) ;
80105} ;
81106
82- const UpdateVersionModal = ( props : {
107+ const ChangeVersionModal = ( props : {
83108 open : boolean ;
84109 onOpenChange : ( open : boolean ) => void ;
85- latestVersion : string ;
86110 instance : EngineInstance ;
111+ currentVersion : string ;
112+ serverVersions : { latest : string ; recent : string [ ] } ;
87113} ) => {
88- const { open, onOpenChange, latestVersion, instance } = props ;
89- const updateDeploymentMutation = useEngineUpdateDeployment ( ) ;
90-
91114 const teamId = "DEBUG - UNIMPLEMENTED" ;
115+ const { open, onOpenChange, instance, currentVersion, serverVersions } =
116+ props ;
117+ const [ selectedVersion , setSelectedVersion ] = useState ( serverVersions . latest ) ;
118+ const updateDeploymentMutation = useEngineUpdateDeployment ( ) ;
92119
93120 if ( ! instance . deploymentId ) {
94- // For self -hosted, show a prompt to the Github release page .
121+ // Self -hosted modal: prompt to update manually .
95122 return (
96123 < Dialog open = { open } onOpenChange = { onOpenChange } >
97124 < DialogContent
@@ -100,7 +127,7 @@ const UpdateVersionModal = (props: {
100127 >
101128 < DialogHeader >
102129 < DialogTitle className = "mb-6 pr-4 font-semibold text-2xl tracking-tight" >
103- Update your self-hosted Engine to { latestVersion }
130+ Update your self-hosted Engine
104131 </ DialogTitle >
105132 < DialogDescription >
106133 View the{ " " }
@@ -121,40 +148,102 @@ const UpdateVersionModal = (props: {
121148 ) ;
122149 }
123150
124- const onClick = async ( ) => {
151+ const onClickUpdate = async ( ) => {
125152 invariant ( instance . deploymentId , "Engine is missing deploymentId." ) ;
126153
127154 try {
128155 const promise = updateDeploymentMutation . mutateAsync ( {
129156 teamId,
130157 deploymentId : instance . deploymentId ,
131- serverVersion : latestVersion ,
158+ serverVersion : selectedVersion ,
132159 } ) ;
133160 toast . promise ( promise , {
134- success : `Upgrading your Engine to ${ latestVersion } . Please confirm after a few minutes .` ,
135- error : "Unexpected error updating your Engine." ,
161+ success : `Updating your Engine to ${ selectedVersion } .` ,
162+ error : "Unexpected error updating Engine." ,
136163 } ) ;
137164 await promise ;
138165 } finally {
139166 onOpenChange ( false ) ;
140167 }
141168 } ;
142169
170+ // For cloud-hosted, prompt the user to select a version to update to.
143171 return (
144172 < Dialog open = { open } onOpenChange = { onOpenChange } >
145173 < DialogContent
146174 className = "z-[10001] max-w-[400px]"
147175 dialogOverlayClassName = "z-[10000]"
148176 >
149177 < DialogHeader >
150- < DialogTitle > Update Engine to { latestVersion } ?</ DialogTitle >
151-
152- < DialogDescription >
153- It is recommended to pause traffic to Engine before performing this
154- upgrade. There is < 1 minute of expected downtime.
155- </ DialogDescription >
178+ < DialogTitle > Update Engine version</ DialogTitle >
156179 </ DialogHeader >
157180
181+ < DialogDescription >
182+ < Select
183+ onValueChange = { ( value ) => setSelectedVersion ( value ) }
184+ value = { selectedVersion }
185+ >
186+ < SelectTrigger >
187+ < SelectValue />
188+ </ SelectTrigger >
189+ < SelectContent className = "z-[10001]" >
190+ < SelectGroup >
191+ < SelectItem
192+ value = { serverVersions . latest }
193+ id = { serverVersions . latest }
194+ >
195+ { serverVersions . latest }
196+ < Badge className = "ml-2" > latest</ Badge >
197+ </ SelectItem >
198+ { serverVersions . recent . map ( ( version ) => {
199+ const isCurrentVersion = version === currentVersion ;
200+ return (
201+ < SelectItem
202+ key = { version }
203+ value = { version }
204+ id = { version }
205+ disabled = { isCurrentVersion }
206+ >
207+ { version }
208+ { isCurrentVersion && (
209+ < Badge className = "ml-2" > current</ Badge >
210+ ) }
211+ </ SelectItem >
212+ ) ;
213+ } ) }
214+ </ SelectGroup >
215+ </ SelectContent >
216+ </ Select >
217+
218+ < div className = "h-4" />
219+
220+ { currentVersion . startsWith ( "v" ) && (
221+ < div >
222+ < TrackedLinkTW
223+ href = { `https://github.com/thirdweb-dev/engine/compare/${ currentVersion } ...${ selectedVersion } ` }
224+ category = "engine"
225+ label = "clicked-engine-releases"
226+ target = "_blank"
227+ className = "text-link-foreground hover:text-foreground"
228+ >
229+ View changes: { currentVersion } → { selectedVersion }
230+ </ TrackedLinkTW >
231+ </ div >
232+ ) }
233+
234+ < div className = "h-4" />
235+
236+ < Alert variant = "warning" >
237+ < TriangleAlertIcon className = "!text-warning-text size-4" />
238+ < AlertTitle > There may be up to 1 minute of downtime.</ AlertTitle >
239+
240+ < AlertDescription className = "!pl-0 pt-2" >
241+ We recommended pausing traffic to Engine before performing this
242+ version update.
243+ </ AlertDescription >
244+ </ Alert >
245+ </ DialogDescription >
246+
158247 < DialogFooter className = "mt-5" >
159248 < Button
160249 type = "button"
@@ -165,7 +254,7 @@ const UpdateVersionModal = (props: {
165254 </ Button >
166255 < Button
167256 type = "submit"
168- onClick = { onClick }
257+ onClick = { onClickUpdate }
169258 variant = "primary"
170259 className = "gap-2"
171260 >
@@ -174,7 +263,7 @@ const UpdateVersionModal = (props: {
174263 ) : (
175264 < CloudDownloadIcon className = "size-4" />
176265 ) }
177- Update
266+ Update to { selectedVersion }
178267 </ Button >
179268 </ DialogFooter >
180269 </ DialogContent >
0 commit comments