@@ -11,6 +11,8 @@ import { ToggleSwitch } from "@/components/ui/toggle-switch"
1111import { renderCloudBenefitsContent } from "./CloudUpsellDialog"
1212import { TriangleAlert } from "lucide-react"
1313import { cn } from "@/lib/utils"
14+ import { Tab , TabContent , TabHeader } from "../common/Tab"
15+ import { Button } from "@/components/ui/button"
1416
1517// Define the production URL constant locally to avoid importing from cloud package in tests
1618const PRODUCTION_ROO_CODE_API_URL = "https://app.roocode.com"
@@ -153,186 +155,187 @@ export const CloudView = ({ userInfo, isAuthenticated, cloudApiUrl, onDone }: Cl
153155 }
154156
155157 return (
156- < div className = "flex flex-col h-full p-4" >
157- < div className = "flex justify-between items-center mb-6" >
158- < h1 className = "text-xl font-medium text-vscode-foreground" > { isAuthenticated && t ( "cloud:title" ) } </ h1 >
159- < VSCodeButton appearance = "secondary" onClick = { onDone } >
160- { t ( "settings:common.done" ) }
161- </ VSCodeButton >
162- </ div >
163- { isAuthenticated ? (
164- < >
165- { userInfo && (
166- < div className = "flex flex-col items-center mb-6" >
167- < div className = "w-16 h-16 mb-3 rounded-full overflow-hidden" >
168- { userInfo ?. picture ? (
169- < img
170- src = { userInfo . picture }
171- alt = { t ( "cloud:profilePicture" ) }
172- className = "w-full h-full object-cover"
173- />
174- ) : (
175- < div className = "w-full h-full flex items-center justify-center bg-vscode-button-background text-vscode-button-foreground text-xl" >
176- { userInfo ?. name ?. charAt ( 0 ) || userInfo ?. email ?. charAt ( 0 ) || "?" }
177- </ div >
178- ) }
179- </ div >
180- { userInfo . name && (
181- < h2 className = "text-lg font-medium text-vscode-foreground my-0" > { userInfo . name } </ h2 >
182- ) }
183- { userInfo ?. email && (
184- < p className = "text-sm text-vscode-descriptionForeground my-0" > { userInfo ?. email } </ p >
185- ) }
186- { userInfo ?. organizationName && (
187- < div className = "flex items-center gap-2 text-sm text-vscode-descriptionForeground mt-2" >
188- { userInfo . organizationImageUrl && (
158+ < Tab >
159+ < TabHeader className = "flex justify-between items-center" >
160+ < h3 className = "text-vscode-foreground m-0" > { isAuthenticated && t ( "cloud:title" ) } </ h3 >
161+ < Button onClick = { onDone } > { t ( "settings:common.done" ) } </ Button >
162+ </ TabHeader >
163+
164+ < TabContent >
165+ { isAuthenticated ? (
166+ < >
167+ { userInfo && (
168+ < div className = "flex flex-col items-center mb-6" >
169+ < div className = "w-16 h-16 mb-3 rounded-full overflow-hidden" >
170+ { userInfo ?. picture ? (
189171 < img
190- src = { userInfo . organizationImageUrl }
191- alt = { userInfo . organizationName }
192- className = "w-4 h-4 rounded object-cover"
172+ src = { userInfo . picture }
173+ alt = { t ( "cloud:profilePicture" ) }
174+ className = "w-full h-full object-cover"
193175 />
176+ ) : (
177+ < div className = "w-full h-full flex items-center justify-center bg-vscode-button-background text-vscode-button-foreground text-xl" >
178+ { userInfo ?. name ?. charAt ( 0 ) || userInfo ?. email ?. charAt ( 0 ) || "?" }
179+ </ div >
194180 ) }
195- < span > { userInfo . organizationName } </ span >
196181 </ div >
197- ) }
198- </ div >
199- ) }
200-
201- { /* Task Sync Toggle - Always shown when authenticated */ }
202- < div className = "border-t border-vscode-widget-border pt-4 mt-4" >
203- < div className = "flex items-center gap-3 mb-2" >
204- < ToggleSwitch
205- checked = { taskSyncEnabled }
206- onChange = { handleTaskSyncToggle }
207- size = "medium"
208- aria-label = { t ( "cloud:taskSync" ) }
209- data-testid = "task-sync-toggle"
210- disabled = { ! ! userInfo ?. organizationId }
211- />
212- < span className = "font-medium text-vscode-foreground" > { t ( "cloud:taskSync" ) } </ span >
213- </ div >
214- < div className = "text-vscode-descriptionForeground text-sm mt-1 mb-4 ml-8" >
215- { t ( "cloud:taskSyncDescription" ) }
216- </ div >
217- { userInfo ?. organizationId && (
218- < div className = "text-vscode-descriptionForeground text-sm mt-1 mb-4 ml-8 italic" >
219- { t ( "cloud:taskSyncManagedByOrganization" ) }
182+ { userInfo . name && (
183+ < h2 className = "text-lg font-medium text-vscode-foreground my-0" > { userInfo . name } </ h2 >
184+ ) }
185+ { userInfo ?. email && (
186+ < p className = "text-sm text-vscode-descriptionForeground my-0" > { userInfo ?. email } </ p >
187+ ) }
188+ { userInfo ?. organizationName && (
189+ < div className = "flex items-center gap-2 text-sm text-vscode-descriptionForeground mt-2" >
190+ { userInfo . organizationImageUrl && (
191+ < img
192+ src = { userInfo . organizationImageUrl }
193+ alt = { userInfo . organizationName }
194+ className = "w-4 h-4 rounded object-cover"
195+ />
196+ ) }
197+ < span > { userInfo . organizationName } </ span >
198+ </ div >
199+ ) }
220200 </ div >
221201 ) }
222202
223- { /* Remote Control Toggle - Only shown when both extensionBridgeEnabled and featureRoomoteControlEnabled are true */ }
224- { userInfo ?. extensionBridgeEnabled && featureRoomoteControlEnabled && (
225- < >
226- < div className = "flex items-center gap-3 mb-2" >
227- < ToggleSwitch
228- checked = { remoteControlEnabled }
229- onChange = { handleRemoteControlToggle }
230- size = "medium"
231- aria-label = { t ( "cloud:remoteControl" ) }
232- data-testid = "remote-control-toggle"
233- disabled = { ! taskSyncEnabled }
234- />
235- < span className = "font-medium text-vscode-foreground" >
236- { t ( "cloud:remoteControl" ) }
237- </ span >
238- </ div >
239- < div className = "text-vscode-descriptionForeground text-sm mt-1 mb-4 ml-8" >
240- { t ( "cloud:remoteControlDescription" ) }
241- { ! taskSyncEnabled && (
242- < div className = "text-vscode-errorForeground mt-2" >
243- { t ( "cloud:remoteControlRequiresTaskSync" ) }
244- </ div >
245- ) }
203+ { /* Task Sync Toggle - Always shown when authenticated */ }
204+ < div className = "border-t border-vscode-widget-border pt-4 mt-4" >
205+ < div className = "flex items-center gap-3 mb-2" >
206+ < ToggleSwitch
207+ checked = { taskSyncEnabled }
208+ onChange = { handleTaskSyncToggle }
209+ size = "medium"
210+ aria-label = { t ( "cloud:taskSync" ) }
211+ data-testid = "task-sync-toggle"
212+ disabled = { ! ! userInfo ?. organizationId }
213+ />
214+ < span className = "font-medium text-vscode-foreground" > { t ( "cloud:taskSync" ) } </ span >
215+ </ div >
216+ < div className = "text-vscode-descriptionForeground text-sm mt-1 mb-4 ml-8" >
217+ { t ( "cloud:taskSyncDescription" ) }
218+ </ div >
219+ { userInfo ?. organizationId && (
220+ < div className = "text-vscode-descriptionForeground text-sm mt-1 mb-4 ml-8 italic" >
221+ { t ( "cloud:taskSyncManagedByOrganization" ) }
246222 </ div >
247- </ >
248- ) }
223+ ) }
249224
250- { /* Info text about usage metrics */ }
251- < div className = "text-vscode-descriptionForeground text-sm mt-4 mb-4 ml-8 italic" >
252- { t ( "cloud:usageMetricsAlwaysReported" ) }
253- </ div >
225+ { /* Remote Control Toggle - Only shown when both extensionBridgeEnabled and featureRoomoteControlEnabled are true */ }
226+ { userInfo ?. extensionBridgeEnabled && featureRoomoteControlEnabled && (
227+ < >
228+ < div className = "flex items-center gap-3 mb-2" >
229+ < ToggleSwitch
230+ checked = { remoteControlEnabled }
231+ onChange = { handleRemoteControlToggle }
232+ size = "medium"
233+ aria-label = { t ( "cloud:remoteControl" ) }
234+ data-testid = "remote-control-toggle"
235+ disabled = { ! taskSyncEnabled }
236+ />
237+ < span className = "font-medium text-vscode-foreground" >
238+ { t ( "cloud:remoteControl" ) }
239+ </ span >
240+ </ div >
241+ < div className = "text-vscode-descriptionForeground text-sm mt-1 mb-4 ml-8" >
242+ { t ( "cloud:remoteControlDescription" ) }
243+ { ! taskSyncEnabled && (
244+ < div className = "text-vscode-errorForeground mt-2" >
245+ { t ( "cloud:remoteControlRequiresTaskSync" ) }
246+ </ div >
247+ ) }
248+ </ div >
249+ </ >
250+ ) }
254251
255- < hr className = "border-vscode-widget-border mb-4" />
256- </ div >
252+ { /* Info text about usage metrics */ }
253+ < div className = "text-vscode-descriptionForeground text-sm mt-4 mb-4 ml-8 italic" >
254+ { t ( "cloud:usageMetricsAlwaysReported" ) }
255+ </ div >
257256
258- < div className = "flex flex-col gap-2 mt-4" >
259- < VSCodeButton appearance = "secondary" onClick = { handleVisitCloudWebsite } className = "w-full" >
260- { t ( "cloud:visitCloudWebsite" ) }
261- </ VSCodeButton >
262- < VSCodeButton appearance = "secondary" onClick = { handleLogoutClick } className = "w-full" >
263- { t ( "cloud:logOut" ) }
264- </ VSCodeButton >
265- </ div >
266- </ >
267- ) : (
268- < >
269- < div className = "flex flex-col items-start gap-4 px-8" >
270- < div className = { cn ( authInProgress && "opacity-50" ) } > { renderCloudBenefitsContent ( t ) } </ div >
257+ < hr className = "border-vscode-widget-border mb-4" />
258+ </ div >
271259
272- { ! authInProgress && (
273- < VSCodeButton appearance = "primary " onClick = { handleConnectClick } className = "w-full" >
274- { t ( "cloud:connect " ) }
260+ < div className = "flex flex-col gap-2 mt-4" >
261+ < VSCodeButton appearance = "secondary " onClick = { handleVisitCloudWebsite } className = "w-full" >
262+ { t ( "cloud:visitCloudWebsite " ) }
275263 </ VSCodeButton >
276- ) }
264+ < VSCodeButton appearance = "secondary" onClick = { handleLogoutClick } className = "w-full" >
265+ { t ( "cloud:logOut" ) }
266+ </ VSCodeButton >
267+ </ div >
268+ </ >
269+ ) : (
270+ < >
271+ < div className = "flex flex-col items-start gap-4 px-8" >
272+ < div className = { cn ( authInProgress && "opacity-50" ) } > { renderCloudBenefitsContent ( t ) } </ div >
277273
278- { /* Manual entry section */ }
279- { authInProgress && ! showManualEntry && (
280- // Timeout message with "Having trouble?" link
281- < div className = "flex flex-col items-start gap-1" >
282- < div className = "flex items-center gap-2 text-base text-vscode-descriptionForeground" >
283- < VSCodeProgressRing className = "size-3 text-vscode-foreground" />
284- { t ( "cloud:authWaiting" ) }
274+ { ! authInProgress && (
275+ < VSCodeButton appearance = "primary" onClick = { handleConnectClick } className = "w-full" >
276+ { t ( "cloud:connect" ) }
277+ </ VSCodeButton >
278+ ) }
279+
280+ { /* Manual entry section */ }
281+ { authInProgress && ! showManualEntry && (
282+ // Timeout message with "Having trouble?" link
283+ < div className = "flex flex-col items-start gap-1" >
284+ < div className = "flex items-center gap-2 text-base text-vscode-descriptionForeground" >
285+ < VSCodeProgressRing className = "size-3 text-vscode-foreground" />
286+ { t ( "cloud:authWaiting" ) }
287+ </ div >
288+ { ! showManualEntry && (
289+ < button
290+ onClick = { handleShowManualEntry }
291+ className = "text-base ml-5 text-vscode-textLink-foreground hover:text-vscode-textLink-activeForeground underline cursor-pointer bg-transparent border-none p-0" >
292+ { t ( "cloud:havingTrouble" ) }
293+ </ button >
294+ ) }
285295 </ div >
286- { ! showManualEntry && (
287- < button
288- onClick = { handleShowManualEntry }
289- className = "text-base ml-5 text-vscode-textLink-foreground hover:text-vscode-textLink-activeForeground underline cursor-pointer bg-transparent border-none p-0" >
290- { t ( "cloud:havingTrouble" ) }
291- </ button >
292- ) }
293- </ div >
294- ) }
296+ ) }
295297
296- { showManualEntry && (
297- // Manual URL entry form
298- < div className = "space-y-2 max-w-72" >
299- < p className = "text-base text-vscode-descriptionForeground" >
300- { t ( "cloud:pasteCallbackUrl" ) }
301- </ p >
302- < VSCodeTextField
303- ref = { manualUrlInputRef as any }
304- value = { manualUrl }
305- onChange = { handleManualUrlChange }
306- onKeyDown = { handleKeyDown }
307- placeholder = "vscode://RooVeterinaryInc.roo-cline/auth/clerk/callback?state=..."
308- className = "w-full"
309- />
310- < p className = "mt-1" >
311- or{ " " }
312- < button
313- onClick = { handleReset }
314- className = "text-base text-vscode-textLink-foreground hover:text-vscode-textLink-activeForeground underline cursor-pointer bg-transparent border-none p-0" >
315- { t ( "cloud:startOver" ) }
316- </ button >
317- </ p >
318- </ div >
319- ) }
320- </ div >
321- </ >
322- ) }
323- { cloudApiUrl && cloudApiUrl !== PRODUCTION_ROO_CODE_API_URL && (
324- < div className = "ml-8 mt-6 flex justify-start" >
325- < div className = "inline-flex items-center gap-2 text-xs" >
326- < TriangleAlert className = "size-4 text-vscode-descriptionForeground" />
327- < span className = "text-vscode-foreground/75" > { t ( "cloud:cloudUrlPillLabel" ) } : </ span >
328- < button
329- onClick = { handleOpenCloudUrl }
330- className = "text-vscode-textLink-foreground hover:text-vscode-textLink-activeForeground underline cursor-pointer bg-transparent border-none p-0" >
331- { cloudApiUrl }
332- </ button >
298+ { showManualEntry && (
299+ // Manual URL entry form
300+ < div className = "space-y-2 max-w-72" >
301+ < p className = "text-base text-vscode-descriptionForeground" >
302+ { t ( "cloud:pasteCallbackUrl" ) }
303+ </ p >
304+ < VSCodeTextField
305+ ref = { manualUrlInputRef as any }
306+ value = { manualUrl }
307+ onChange = { handleManualUrlChange }
308+ onKeyDown = { handleKeyDown }
309+ placeholder = "vscode://RooVeterinaryInc.roo-cline/auth/clerk/callback?state=..."
310+ className = "w-full"
311+ />
312+ < p className = "mt-1" >
313+ or{ " " }
314+ < button
315+ onClick = { handleReset }
316+ className = "text-base text-vscode-textLink-foreground hover:text-vscode-textLink-activeForeground underline cursor-pointer bg-transparent border-none p-0" >
317+ { t ( "cloud:startOver" ) }
318+ </ button >
319+ </ p >
320+ </ div >
321+ ) }
322+ </ div >
323+ </ >
324+ ) }
325+ { cloudApiUrl && cloudApiUrl !== PRODUCTION_ROO_CODE_API_URL && (
326+ < div className = "ml-8 mt-6 flex justify-start" >
327+ < div className = "inline-flex items-center gap-2 text-xs" >
328+ < TriangleAlert className = "size-4 text-vscode-descriptionForeground" />
329+ < span className = "text-vscode-foreground/75" > { t ( "cloud:cloudUrlPillLabel" ) } : </ span >
330+ < button
331+ onClick = { handleOpenCloudUrl }
332+ className = "text-vscode-textLink-foreground hover:text-vscode-textLink-activeForeground underline cursor-pointer bg-transparent border-none p-0" >
333+ { cloudApiUrl }
334+ </ button >
335+ </ div >
333336 </ div >
334- </ div >
335- ) }
336- </ div >
337+ ) }
338+ </ TabContent >
339+ </ Tab >
337340 )
338341}
0 commit comments