Skip to content

Commit 6af9324

Browse files
committed
Makes upsell dialog visibility available outside the component to fix an issue where <RooTips /> would be incorrectly hidden.
1 parent d54ff8a commit 6af9324

File tree

9 files changed

+1072
-211
lines changed

9 files changed

+1072
-211
lines changed

webview-ui/src/components/chat/ChatView.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ import RooTips from "@src/components/welcome/RooTips"
4040
import { StandardTooltip } from "@src/components/ui"
4141
import { useAutoApprovalState } from "@src/hooks/useAutoApprovalState"
4242
import { useAutoApprovalToggles } from "@src/hooks/useAutoApprovalToggles"
43-
import { CloudUpsellDialog } from "@src/components/cloud/CloudUpsellDialog"
4443

4544
import TelemetryBanner from "../common/TelemetryBanner"
4645
import VersionIndicator from "../common/VersionIndicator"
@@ -55,9 +54,12 @@ import SystemPromptWarning from "./SystemPromptWarning"
5554
import ProfileViolationWarning from "./ProfileViolationWarning"
5655
import { CheckpointWarning } from "./CheckpointWarning"
5756
import { QueuedMessages } from "./QueuedMessages"
57+
import { Cloud } from "lucide-react"
58+
59+
import { CloudUpsellDialog } from "@src/components/cloud/CloudUpsellDialog"
5860
import DismissibleUpsell from "../common/DismissibleUpsell"
5961
import { useCloudUpsell } from "@src/hooks/useCloudUpsell"
60-
import { Cloud } from "lucide-react"
62+
import { useUpsellVisibility, UPSELL_IDS } from "@/hooks/useUpsellVisibility"
6163

6264
export interface ChatViewProps {
6365
isHidden: boolean
@@ -1772,6 +1774,8 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
17721774

17731775
const areButtonsVisible = showScrollToBottom || primaryButtonText || secondaryButtonText || isStreaming
17741776

1777+
const isCloudUpsellVisible = useUpsellVisibility(UPSELL_IDS.TASK_LIST)
1778+
17751779
return (
17761780
<div
17771781
data-testid="chat-view"
@@ -1842,12 +1846,12 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
18421846
<RooHero />
18431847

18441848
<div className="mb-2.5">
1845-
{cloudIsAuthenticated || taskHistory.length < 4 ? (
1849+
{cloudIsAuthenticated || (tasks.length === 0 && !isCloudUpsellVisible) ? (
18461850
<RooTips />
18471851
) : (
18481852
<>
18491853
<DismissibleUpsell
1850-
upsellId="taskList"
1854+
upsellId={UPSELL_IDS.TASK_LIST}
18511855
icon={<Cloud className="size-4 mt-0.5 shrink-0" />}
18521856
onClick={() => openUpsell()}
18531857
dismissOnClick={false}

webview-ui/src/components/chat/TaskHeader.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { memo, useEffect, useRef, useState } from "react"
22
import { useTranslation } from "react-i18next"
33
import { useCloudUpsell } from "@src/hooks/useCloudUpsell"
44
import { CloudUpsellDialog } from "@src/components/cloud/CloudUpsellDialog"
5-
import DismissibleUpsell from "@src/components/common/DismissibleUpsell"
65
import { FoldVertical, ChevronUp, ChevronDown } from "lucide-react"
76
import prettyBytes from "pretty-bytes"
87

@@ -24,6 +23,9 @@ import { ContextWindowProgress } from "./ContextWindowProgress"
2423
import { Mention } from "./Mention"
2524
import { TodoListDisplay } from "./TodoListDisplay"
2625

26+
import DismissibleUpsell from "@src/components/common/DismissibleUpsell"
27+
import { UPSELL_IDS } from "@/constants/upsellIds"
28+
2729
export interface TaskHeaderProps {
2830
task: ClineMessage
2931
tokensIn: number
@@ -103,7 +105,7 @@ const TaskHeader = ({
103105
<div className="pt-2 pb-0 px-3">
104106
{showLongRunningTaskMessage && !isTaskComplete && (
105107
<DismissibleUpsell
106-
upsellId="longRunningTask"
108+
upsellId={UPSELL_IDS.LONG_RUNNING_TASK}
107109
onClick={() => openUpsell()}
108110
dismissOnClick={false}
109111
variant="banner">

webview-ui/src/components/common/DismissibleUpsell.tsx

Lines changed: 24 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { memo, ReactNode, useEffect, useState, useRef } from "react"
2-
import { vscode } from "@src/utils/vscode"
1+
import { memo, ReactNode } from "react"
32
import { useAppTranslation } from "@src/i18n/TranslationContext"
3+
import { DismissedUpsellsProvider, useDismissedUpsells } from "@src/context/DismissedUpsellsContext"
44

55
interface DismissibleUpsellProps {
66
/** Required unique identifier for this upsell */
@@ -32,7 +32,8 @@ const DismissIcon = () => (
3232
</svg>
3333
)
3434

35-
const DismissibleUpsell = memo(
35+
// Internal component that uses the context
36+
const DismissibleUpsellInternal = memo(
3637
({
3738
upsellId,
3839
className,
@@ -44,55 +45,21 @@ const DismissibleUpsell = memo(
4445
dismissOnClick = false,
4546
}: DismissibleUpsellProps) => {
4647
const { t } = useAppTranslation()
47-
const [isVisible, setIsVisible] = useState(false)
48-
const isMountedRef = useRef(true)
48+
const { isUpsellVisible, dismissUpsell, isLoading } = useDismissedUpsells()
4949

50-
useEffect(() => {
51-
// Track mounted state
52-
isMountedRef.current = true
50+
// Check if this upsell is visible
51+
const isVisible = isUpsellVisible(upsellId)
5352

54-
// Request the current list of dismissed upsells from the extension
55-
vscode.postMessage({ type: "getDismissedUpsells" })
56-
57-
// Listen for the response
58-
const handleMessage = (event: MessageEvent) => {
59-
// Only update state if component is still mounted
60-
if (!isMountedRef.current) return
61-
62-
const message = event.data
63-
// Add null/undefined check for message
64-
if (message && message.type === "dismissedUpsells" && Array.isArray(message.list)) {
65-
// Check if this upsell has been dismissed
66-
if (!message.list.includes(upsellId)) {
67-
setIsVisible(true)
68-
}
69-
}
70-
}
71-
72-
window.addEventListener("message", handleMessage)
73-
return () => {
74-
isMountedRef.current = false
75-
window.removeEventListener("message", handleMessage)
76-
}
77-
}, [upsellId])
78-
79-
const handleDismiss = async () => {
80-
// First notify the extension to persist the dismissal
81-
// This ensures the message is sent even if the component unmounts quickly
82-
vscode.postMessage({
83-
type: "dismissUpsell",
84-
upsellId: upsellId,
85-
})
86-
87-
// Then hide the upsell
88-
setIsVisible(false)
53+
const handleDismiss = () => {
54+
// Dismiss the upsell through the context
55+
dismissUpsell(upsellId)
8956

9057
// Call the optional callback
9158
onDismiss?.()
9259
}
9360

94-
// Don't render if not visible
95-
if (!isVisible) {
61+
// Don't render if not visible or still loading
62+
if (!isVisible || isLoading) {
9663
return null
9764
}
9865

@@ -107,6 +74,7 @@ const DismissibleUpsell = memo(
10774
button: "text-vscode-notifications-foreground",
10875
},
10976
}
77+
11078
// Build container classes based on variant and presence of click handler
11179
const containerClasses = [
11280
"relative flex items-start justify-between gap-2",
@@ -158,6 +126,17 @@ const DismissibleUpsell = memo(
158126
},
159127
)
160128

129+
DismissibleUpsellInternal.displayName = "DismissibleUpsellInternal"
130+
131+
// Wrapper component that provides the context
132+
const DismissibleUpsell = memo((props: DismissibleUpsellProps) => {
133+
return (
134+
<DismissedUpsellsProvider>
135+
<DismissibleUpsellInternal {...props} />
136+
</DismissedUpsellsProvider>
137+
)
138+
})
139+
161140
DismissibleUpsell.displayName = "DismissibleUpsell"
162141

163142
export default DismissibleUpsell

0 commit comments

Comments
 (0)