mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
feat: add Clerk account switcher to CloudView
- Install @clerk/clerk-react package - Create AccountSwitcher component placeholder - Integrate AccountSwitcher below organizationName in CloudView - Add tests for AccountSwitcher display logic - Note: Full Clerk integration requires backend auth service setup
This commit is contained in:
parent
87b45def18
commit
1ab4023273
5 changed files with 189 additions and 9 deletions
81
pnpm-lock.yaml
generated
81
pnpm-lock.yaml
generated
|
|
@ -930,6 +930,9 @@ importers:
|
|||
|
||||
webview-ui:
|
||||
dependencies:
|
||||
'@clerk/clerk-react':
|
||||
specifier: ^5.47.0
|
||||
version: 5.47.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@radix-ui/react-alert-dialog':
|
||||
specifier: ^1.1.6
|
||||
version: 1.1.13(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
|
|
@ -1593,6 +1596,29 @@ packages:
|
|||
'@chevrotain/utils@11.0.3':
|
||||
resolution: {integrity: sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==}
|
||||
|
||||
'@clerk/clerk-react@5.47.0':
|
||||
resolution: {integrity: sha512-of2Y6dg36eL7TwAP4DbGOMWW6DJpJSIuCn6g1jJqJkh4NGljHC7vz3H18OERRM5UQXmBG3twjC8CNAQxQrquRA==}
|
||||
engines: {node: '>=18.17.0'}
|
||||
peerDependencies:
|
||||
react: ^18.0.0 || ^19.0.0 || ^19.0.0-0
|
||||
react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-0
|
||||
|
||||
'@clerk/shared@3.25.0':
|
||||
resolution: {integrity: sha512-2Vb6NQqBA+1g7kfGct/OlSFmzU54/s4BQp3qeHwDqW1FgaU4MuXbqfBClI6AatxOC8Ux8W16Rvf705ViwFSxlw==}
|
||||
engines: {node: '>=18.17.0'}
|
||||
peerDependencies:
|
||||
react: ^18.0.0 || ^19.0.0 || ^19.0.0-0
|
||||
react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-0
|
||||
peerDependenciesMeta:
|
||||
react:
|
||||
optional: true
|
||||
react-dom:
|
||||
optional: true
|
||||
|
||||
'@clerk/types@4.86.0':
|
||||
resolution: {integrity: sha512-YFaOYIAZWbpXehAmtgUB0YNf1v5b/hlwePvdqxlD5vdwrNsap28RpupWZat0hp1+PTtb9uAwSa5AFCOxkYLUJQ==}
|
||||
engines: {node: '>=18.17.0'}
|
||||
|
||||
'@corex/deepmerge@4.0.43':
|
||||
resolution: {integrity: sha512-N8uEMrMPL0cu/bdboEWpQYb/0i2K5Qn8eCsxzOmxSggJbbQte7ljMRoXm917AbntqTGOzdTu+vP3KOOzoC70HQ==}
|
||||
|
||||
|
|
@ -6203,6 +6229,9 @@ packages:
|
|||
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
||||
glob-to-regexp@0.4.1:
|
||||
resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==}
|
||||
|
||||
glob@10.4.5:
|
||||
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
|
||||
hasBin: true
|
||||
|
|
@ -6830,6 +6859,10 @@ packages:
|
|||
js-cookie@2.2.1:
|
||||
resolution: {integrity: sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==}
|
||||
|
||||
js-cookie@3.0.5:
|
||||
resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
js-message@1.0.7:
|
||||
resolution: {integrity: sha512-efJLHhLjIyKRewNS9EGZ4UpI8NguuL6fKkhRxVuMmrGV2xN/0APGdQYwLFky5w9naebSZ0OwAGp0G6/2Cg90rA==}
|
||||
engines: {node: '>=0.6.0'}
|
||||
|
|
@ -9169,6 +9202,11 @@ packages:
|
|||
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
swr@2.3.4:
|
||||
resolution: {integrity: sha512-bYd2lrhc+VarcpkgWclcUi92wYCpOgMws9Sd1hG1ntAu0NEy+14CbotuFjshBU2kt9rYj9TSmDcybpxpeTU1fg==}
|
||||
peerDependencies:
|
||||
react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
symbol-tree@3.2.4:
|
||||
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
|
||||
|
||||
|
|
@ -9649,6 +9687,11 @@ packages:
|
|||
peerDependencies:
|
||||
react: '>=16.8'
|
||||
|
||||
use-sync-external-store@1.5.0:
|
||||
resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
utf8-byte-length@1.0.5:
|
||||
resolution: {integrity: sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==}
|
||||
|
||||
|
|
@ -11052,6 +11095,30 @@ snapshots:
|
|||
|
||||
'@chevrotain/utils@11.0.3': {}
|
||||
|
||||
'@clerk/clerk-react@5.47.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@clerk/shared': 3.25.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@clerk/types': 4.86.0
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
tslib: 2.8.1
|
||||
|
||||
'@clerk/shared@3.25.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@clerk/types': 4.86.0
|
||||
dequal: 2.0.3
|
||||
glob-to-regexp: 0.4.1
|
||||
js-cookie: 3.0.5
|
||||
std-env: 3.9.0
|
||||
swr: 2.3.4(react@18.3.1)
|
||||
optionalDependencies:
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
|
||||
'@clerk/types@4.86.0':
|
||||
dependencies:
|
||||
csstype: 3.1.3
|
||||
|
||||
'@corex/deepmerge@4.0.43': {}
|
||||
|
||||
'@csstools/color-helpers@5.0.2': {}
|
||||
|
|
@ -16048,6 +16115,8 @@ snapshots:
|
|||
dependencies:
|
||||
is-glob: 4.0.3
|
||||
|
||||
glob-to-regexp@0.4.1: {}
|
||||
|
||||
glob@10.4.5:
|
||||
dependencies:
|
||||
foreground-child: 3.3.1
|
||||
|
|
@ -16731,6 +16800,8 @@ snapshots:
|
|||
|
||||
js-cookie@2.2.1: {}
|
||||
|
||||
js-cookie@3.0.5: {}
|
||||
|
||||
js-message@1.0.7: {}
|
||||
|
||||
js-queue@2.0.2:
|
||||
|
|
@ -19581,6 +19652,12 @@ snapshots:
|
|||
|
||||
supports-preserve-symlinks-flag@1.0.0: {}
|
||||
|
||||
swr@2.3.4(react@18.3.1):
|
||||
dependencies:
|
||||
dequal: 2.0.3
|
||||
react: 18.3.1
|
||||
use-sync-external-store: 1.5.0(react@18.3.1)
|
||||
|
||||
symbol-tree@3.2.4: {}
|
||||
|
||||
tabbable@5.3.3: {}
|
||||
|
|
@ -20104,6 +20181,10 @@ snapshots:
|
|||
howler: 2.2.4
|
||||
react: 18.3.1
|
||||
|
||||
use-sync-external-store@1.5.0(react@18.3.1):
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
|
||||
utf8-byte-length@1.0.5: {}
|
||||
|
||||
util-deprecate@1.0.2: {}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
"clean": "rimraf ../src/webview-ui ../apps/vscode-nightly/build/webview-ui tsconfig.tsbuildinfo .turbo"
|
||||
},
|
||||
"dependencies": {
|
||||
"@clerk/clerk-react": "^5.47.0",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.6",
|
||||
"@radix-ui/react-checkbox": "^1.1.5",
|
||||
"@radix-ui/react-collapsible": "^1.1.3",
|
||||
|
|
|
|||
40
webview-ui/src/components/cloud/ClerkProvider.tsx
Normal file
40
webview-ui/src/components/cloud/ClerkProvider.tsx
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import React from "react"
|
||||
|
||||
interface ClerkProviderProps {
|
||||
children: React.ReactNode
|
||||
userInfo?: {
|
||||
id?: string
|
||||
organizationId?: string
|
||||
} | null
|
||||
}
|
||||
|
||||
export const ClerkProvider: React.FC<ClerkProviderProps> = ({ children }) => {
|
||||
// Since we're in a VSCode extension webview, we don't have direct access to Clerk auth
|
||||
// The authentication is handled by the backend WebAuthService
|
||||
// We'll render the OrganizationSwitcher as a UI component only
|
||||
|
||||
// For now, we'll just pass through the children
|
||||
// The actual Clerk integration would require backend support
|
||||
return <>{children}</>
|
||||
}
|
||||
|
||||
export const AccountSwitcher: React.FC<{
|
||||
organizationId?: string
|
||||
organizationName?: string
|
||||
}> = ({ organizationId, organizationName }) => {
|
||||
// Since we can't directly use Clerk's OrganizationSwitcher without proper auth setup,
|
||||
// we'll create a placeholder that shows the current organization
|
||||
// In a full implementation, this would integrate with the backend auth service
|
||||
|
||||
if (!organizationId || !organizationName) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2 px-3 py-2 rounded-md border border-vscode-widget-border bg-vscode-dropdown-background">
|
||||
<span className="text-sm text-vscode-foreground">Organization:</span>
|
||||
<span className="text-sm font-medium text-vscode-foreground">{organizationName}</span>
|
||||
<span className="text-xs text-vscode-descriptionForeground ml-auto">(Switch via Roo Code Cloud)</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ import { TriangleAlert } from "lucide-react"
|
|||
import { cn } from "@/lib/utils"
|
||||
import { Tab, TabContent, TabHeader } from "../common/Tab"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { AccountSwitcher } from "./ClerkProvider"
|
||||
|
||||
// Define the production URL constant locally to avoid importing from cloud package in tests
|
||||
const PRODUCTION_ROO_CODE_API_URL = "https://app.roocode.com"
|
||||
|
|
@ -186,15 +187,21 @@ export const CloudView = ({ userInfo, isAuthenticated, cloudApiUrl, onDone }: Cl
|
|||
<p className="text-sm text-vscode-descriptionForeground my-0">{userInfo?.email}</p>
|
||||
)}
|
||||
{userInfo?.organizationName && (
|
||||
<div className="flex items-center gap-2 text-sm text-vscode-descriptionForeground mt-2">
|
||||
{userInfo.organizationImageUrl && (
|
||||
<img
|
||||
src={userInfo.organizationImageUrl}
|
||||
alt={userInfo.organizationName}
|
||||
className="w-4 h-4 rounded object-cover"
|
||||
/>
|
||||
)}
|
||||
<span>{userInfo.organizationName}</span>
|
||||
<div className="flex flex-col gap-2 mt-2">
|
||||
<div className="flex items-center gap-2 text-sm text-vscode-descriptionForeground">
|
||||
{userInfo.organizationImageUrl && (
|
||||
<img
|
||||
src={userInfo.organizationImageUrl}
|
||||
alt={userInfo.organizationName}
|
||||
className="w-4 h-4 rounded object-cover"
|
||||
/>
|
||||
)}
|
||||
<span>{userInfo.organizationName}</span>
|
||||
</div>
|
||||
<AccountSwitcher
|
||||
organizationId={userInfo.organizationId}
|
||||
organizationName={userInfo.organizationName}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,14 @@ import { render, screen } from "@/utils/test-utils"
|
|||
|
||||
import { CloudView } from "../CloudView"
|
||||
|
||||
// Mock the AccountSwitcher component
|
||||
vi.mock("../ClerkProvider", () => ({
|
||||
AccountSwitcher: ({ organizationId, organizationName }: any) => {
|
||||
if (!organizationId || !organizationName) return null
|
||||
return <div data-testid="account-switcher">Organization: {organizationName} (Switch via Roo Code Cloud)</div>
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock the translation context
|
||||
vi.mock("@src/i18n/TranslationContext", () => ({
|
||||
useAppTranslation: () => ({
|
||||
|
|
@ -360,4 +368,47 @@ describe("CloudView", () => {
|
|||
expect(taskSyncToggle).toHaveAttribute("aria-checked", "true")
|
||||
expect(taskSyncToggle).toHaveAttribute("tabindex", "-1")
|
||||
})
|
||||
|
||||
it("should display AccountSwitcher when user has organization", () => {
|
||||
const mockUserInfo = {
|
||||
name: "Test User",
|
||||
email: "test@example.com",
|
||||
organizationId: "org-123",
|
||||
organizationName: "Test Organization",
|
||||
}
|
||||
|
||||
render(
|
||||
<CloudView
|
||||
userInfo={mockUserInfo}
|
||||
isAuthenticated={true}
|
||||
cloudApiUrl="https://app.roocode.com"
|
||||
onDone={() => {}}
|
||||
/>,
|
||||
)
|
||||
|
||||
// Check that the AccountSwitcher is displayed
|
||||
const accountSwitcher = screen.getByTestId("account-switcher")
|
||||
expect(accountSwitcher).toBeInTheDocument()
|
||||
expect(accountSwitcher).toHaveTextContent("Organization: Test Organization (Switch via Roo Code Cloud)")
|
||||
})
|
||||
|
||||
it("should not display AccountSwitcher when user has no organization", () => {
|
||||
const mockUserInfo = {
|
||||
name: "Test User",
|
||||
email: "test@example.com",
|
||||
// No organizationId or organizationName
|
||||
}
|
||||
|
||||
render(
|
||||
<CloudView
|
||||
userInfo={mockUserInfo}
|
||||
isAuthenticated={true}
|
||||
cloudApiUrl="https://app.roocode.com"
|
||||
onDone={() => {}}
|
||||
/>,
|
||||
)
|
||||
|
||||
// Check that the AccountSwitcher is NOT displayed
|
||||
expect(screen.queryByTestId("account-switcher")).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue