Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 61 additions & 4 deletions apps/web-roo-code/src/components/homepage/install-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,50 @@
import { VscVscode } from "react-icons/vsc"
import Link from "next/link"
import { motion } from "framer-motion"
import { Copy, Check } from "lucide-react"
import { useState, useEffect, useRef } from "react"

interface InstallSectionProps {
downloads: string | null
}

export function InstallSection({ downloads }: InstallSectionProps) {
const [copied, setCopied] = useState(false)
const installCmd = "code --install-extension RooVeterinaryInc.roo-cline"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] Optional: extract installCmd to a shared constant (e.g., apps/web-roo-code/src/lib/constants.ts) to keep it DRY across the site.


const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)

useEffect(() => {
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current)
}
}
}, [])

const handleCopy = async () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] Optional: feature-detect isSecureContext and navigator.clipboard?.writeText before calling Clipboard API to avoid relying on exceptions for control flow.

try {
await navigator.clipboard.writeText(installCmd)
setCopied(true)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In handleCopy, the logic for setting the copied state and timeout is duplicated in both try and fallback blocks. Also, consider clearing any existing timeout before setting a new one to avoid potential overlaps on rapid clicks.

const id = setTimeout(() => setCopied(false), 1000)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Clear any existing timeout before scheduling a new one to avoid overlapping timers and stale state on rapid clicks.

timeoutRef.current = id
} catch (_e) {
// Fallback for environments without Clipboard API support
const textarea = document.createElement("textarea")
textarea.value = installCmd
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] In fallback, set textarea to readonly and position it offscreen (fixed, left:-9999px) before appendChild to avoid layout shift and improve reliability.

document.body.appendChild(textarea)
textarea.select()
try {
document.execCommand("copy")
setCopied(true)
const id = setTimeout(() => setCopied(false), 1000)
timeoutRef.current = id
} finally {
document.body.removeChild(textarea)
}
}
}

const backgroundVariants = {
hidden: {
opacity: 0,
Expand Down Expand Up @@ -84,12 +122,31 @@ export function InstallSection({ downloads }: InstallSectionProps) {
<div className="absolute -inset-px rounded-xl bg-gradient-to-r from-blue-500/50 via-cyan-500/50 to-purple-500/50 opacity-30 blur-sm transition-all duration-500 group-hover:opacity-60 dark:opacity-40 dark:group-hover:opacity-70" />
<div className="relative overflow-hidden rounded-xl border border-border bg-background/80 shadow-lg backdrop-blur-xl transition-all duration-500 ease-out group-hover:border-blue-500/50 group-hover:shadow-xl group-hover:shadow-blue-500/10 dark:border-border/50 dark:bg-background/60 dark:group-hover:border-blue-400/50">
<div className="border-b border-border/50 bg-muted/30 px-4 py-3 dark:bg-muted/20">
<div className="text-sm font-medium text-foreground">or via CLI</div>
<div className="flex items-center justify-center">
<div className="text-sm font-medium text-foreground text-center">
or via CLI
</div>
</div>
</div>
<div className="overflow-x-auto bg-background/50 dark:bg-background/30">
<pre className="p-4">
<div className="relative overflow-x-auto bg-background/50 dark:bg-background/30">
<button
type="button"
onClick={handleCopy}
className="absolute right-3 top-3 inline-flex items-center justify-center rounded-md p-2 text-foreground/80 hover:text-foreground hover:bg-foreground/5 transition-colors"
aria-label={copied ? "Copied" : "Copy install command to clipboard"}
title={copied ? "Copied!" : "Copy"}>
{copied ? (
<Check className="h-4 w-4 text-green-500" />
) : (
<Copy className="h-4 w-4" />
)}
<span className="sr-only" aria-live="polite" role="status">
{copied ? "Copied" : "Copy"}
</span>
</button>
<pre className="p-4 pr-12">
<code className="whitespace-pre-wrap break-all text-sm font-mono text-foreground sm:break-normal sm:text-base">
code --install-extension RooVeterinaryInc.roo-cline
{installCmd}
</code>
</pre>
</div>
Expand Down