Skip to content
Closed
Changes from 2 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
59 changes: 57 additions & 2 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,29 @@ 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-between">
<div className="text-sm font-medium text-foreground">or via CLI</div>
<button
Copy link
Contributor

Choose a reason for hiding this comment

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

UI labels (aria-label, title, and sr-only text) are hardcoded in English. Consider using the project’s translation function (e.g. t('...')) to support internationalization.

This comment was generated because it violated a code review rule: irule_C0ez7Rji6ANcGkkX.

type="button"
onClick={handleCopy}
className="inline-flex items-center gap-2 rounded-md px-2 py-1 text-xs font-medium 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"}
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 a11y: use a more descriptive SR announcement like 'Copied to clipboard' rather than 'Copied'.

</span>
</button>
</div>
</div>
<div className="overflow-x-auto bg-background/50 dark:bg-background/30">
<pre className="p-4">
<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