-
Notifications
You must be signed in to change notification settings - Fork 2.6k
feat: add CloudAccountSwitcher component for switching between cloud accounts #8221
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,6 +30,7 @@ import { | |
| type TerminalActionPromptType, | ||
| type HistoryItem, | ||
| type CloudUserInfo, | ||
| type CloudOrganizationMembership, | ||
| type CreateTaskOptions, | ||
| type TokenUsage, | ||
| RooCodeEventName, | ||
|
|
@@ -1821,6 +1822,18 @@ export class ClineProvider | |
| const mergedDeniedCommands = this.mergeDeniedCommands(deniedCommands) | ||
| const cwd = this.cwd | ||
|
|
||
| // Get organization memberships for the account switcher | ||
| let cloudOrganizationMemberships: CloudOrganizationMembership[] = [] | ||
| try { | ||
| if (CloudService.hasInstance() && CloudService.instance.isAuthenticated()) { | ||
| cloudOrganizationMemberships = await CloudService.instance.getOrganizationMemberships() | ||
| } | ||
| } catch (error) { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Error handling: Consider showing a degraded UI state if fetching organization memberships fails, rather than just logging the error. Users might wonder why the account switcher isn't appearing. |
||
| console.error( | ||
| `[getStateToPostToWebview] failed to get organization memberships: ${error instanceof Error ? error.message : String(error)}`, | ||
| ) | ||
| } | ||
|
|
||
| // Check if there's a system prompt override for the current mode | ||
| const currentMode = mode ?? defaultModeSlug | ||
| const hasSystemPromptOverride = await this.hasFileBasedSystemPromptOverride(currentMode) | ||
|
|
@@ -1915,6 +1928,7 @@ export class ClineProvider | |
| hasSystemPromptOverride, | ||
| historyPreviewCollapsed: historyPreviewCollapsed ?? false, | ||
| cloudUserInfo, | ||
| cloudOrganizationMemberships, | ||
| cloudIsAuthenticated: cloudIsAuthenticated ?? false, | ||
| sharingEnabled: sharingEnabled ?? false, | ||
| organizationAllowList, | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,96 @@ | ||||||||||||||||
| import React, { useMemo } from "react" | ||||||||||||||||
| import { User } from "lucide-react" | ||||||||||||||||
|
|
||||||||||||||||
| import { cn } from "@src/lib/utils" | ||||||||||||||||
| import { vscode } from "@src/utils/vscode" | ||||||||||||||||
| import { useAppTranslation } from "@/i18n/TranslationContext" | ||||||||||||||||
|
|
||||||||||||||||
| import type { CloudUserInfo, CloudOrganizationMembership } from "@roo-code/types" | ||||||||||||||||
|
|
||||||||||||||||
| import { StandardTooltip, Button } from "@src/components/ui" | ||||||||||||||||
|
|
||||||||||||||||
| interface CloudAccountSwitcherProps { | ||||||||||||||||
| className?: string | ||||||||||||||||
| userInfo: CloudUserInfo | null | ||||||||||||||||
| organizationMemberships?: CloudOrganizationMembership[] | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| export const CloudAccountSwitcher: React.FC<CloudAccountSwitcherProps> = ({ | ||||||||||||||||
| className, | ||||||||||||||||
| userInfo, | ||||||||||||||||
| organizationMemberships = [], | ||||||||||||||||
| }) => { | ||||||||||||||||
| const { t } = useAppTranslation() | ||||||||||||||||
|
|
||||||||||||||||
| // Calculate if user has multiple accounts | ||||||||||||||||
| // User has multiple accounts if: | ||||||||||||||||
| // 1. They have 2+ organization memberships, OR | ||||||||||||||||
| // 2. They have 1+ organization membership and can also use personal account | ||||||||||||||||
| const hasMultipleAccounts = useMemo(() => { | ||||||||||||||||
| if (!userInfo) return false | ||||||||||||||||
|
|
||||||||||||||||
| // If user has multiple org memberships | ||||||||||||||||
| if (organizationMemberships.length >= 2) return true | ||||||||||||||||
|
|
||||||||||||||||
| // If user has at least one org membership, they also have personal account access | ||||||||||||||||
| if (organizationMemberships.length >= 1) return true | ||||||||||||||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Visibility logic concern: This component will show for users with just one organization membership, even if they can't actually switch to a personal account. Consider adding a check to verify if users can actually switch between accounts (e.g., check if personal account access is available).
Suggested change
|
||||||||||||||||
|
|
||||||||||||||||
| return false | ||||||||||||||||
| }, [userInfo, organizationMemberships]) | ||||||||||||||||
|
|
||||||||||||||||
| // Don't render if user doesn't have multiple accounts | ||||||||||||||||
| if (!hasMultipleAccounts) { | ||||||||||||||||
| return null | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| const handleAccountSwitch = () => { | ||||||||||||||||
| vscode.postMessage({ type: "rooCloudAccountSwitch" }) | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| // Determine which icon to show | ||||||||||||||||
| const renderAccountIcon = () => { | ||||||||||||||||
| // If in organization context and has org image | ||||||||||||||||
| if (userInfo?.organizationId && userInfo?.organizationImageUrl) { | ||||||||||||||||
| return ( | ||||||||||||||||
| <img | ||||||||||||||||
| src={userInfo.organizationImageUrl} | ||||||||||||||||
| alt={userInfo.organizationName || "Organization"} | ||||||||||||||||
| className="w-4 h-4 rounded-full object-cover" | ||||||||||||||||
| /> | ||||||||||||||||
| ) | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| // If user has profile picture | ||||||||||||||||
| if (userInfo?.picture) { | ||||||||||||||||
| return ( | ||||||||||||||||
| <img | ||||||||||||||||
| src={userInfo.picture} | ||||||||||||||||
| alt={userInfo.name || userInfo.email || "User"} | ||||||||||||||||
| className="w-4 h-4 rounded-full object-cover" | ||||||||||||||||
| /> | ||||||||||||||||
| ) | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| // Default user icon | ||||||||||||||||
| return <User className="w-4 h-4" /> | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| return ( | ||||||||||||||||
| <StandardTooltip content={t("cloud:switchAccount")}> | ||||||||||||||||
| <Button | ||||||||||||||||
| variant="ghost" | ||||||||||||||||
| size="sm" | ||||||||||||||||
| onClick={handleAccountSwitch} | ||||||||||||||||
| aria-label={t("cloud:switchAccount")} | ||||||||||||||||
| className={cn( | ||||||||||||||||
| "relative h-5 w-5 p-0", | ||||||||||||||||
| "text-vscode-foreground opacity-85", | ||||||||||||||||
| "hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)]", | ||||||||||||||||
| "focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder", | ||||||||||||||||
| className, | ||||||||||||||||
| )}> | ||||||||||||||||
| {renderAccountIcon()} | ||||||||||||||||
| </Button> | ||||||||||||||||
| </StandardTooltip> | ||||||||||||||||
| ) | ||||||||||||||||
| } | ||||||||||||||||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -5,6 +5,7 @@ | |||||
| "testApiAuthentication": "Test API Authentication", | ||||||
| "signIn": "Connect to Roo Code Cloud", | ||||||
| "connect": "Get started", | ||||||
| "switchAccount": "Switch Roo Cloud Account", | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typographical suggestion: The new label "Switch Roo Cloud Account" appears to be inconsistent with other entries that reference "Roo Code Cloud" (e.g., "Connect to Roo Code Cloud"). If this is unintentional, consider updating it to "Switch Roo Code Cloud Account" for consistency.
Suggested change
This comment was generated because it violated the following rules: irule_C0ez7Rji6ANcGkkX and irule_VrRKWqywZ2YV2SOE. |
||||||
| "cloudBenefitsTitle": "Try Roo Code Cloud", | ||||||
| "cloudBenefitWalkaway": "Follow and control tasks from anywhere (including your phone)", | ||||||
| "cloudBenefitSharing": "Share tasks with others", | ||||||
|
|
||||||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Type safety: Consider using a more explicit interface check instead of duck typing with the 'in' operator. You could define an interface that includes getOrganizationMemberships method.