Skip to content
Open
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
54 changes: 44 additions & 10 deletions client/src/module/student/opensource/RepoDiscoveryPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
GitFork,
CircleDot,
ExternalLink,
Copy,
Check,
X,
ChevronDown,
TrendingUp,
Expand Down Expand Up @@ -58,6 +60,7 @@ export default function RepoDiscoveryPage() {
const [sortKey, setSortKey] = useState("stars");
const [showFilters, setShowFilters] = useState(false);
const [selectedRepo, setSelectedRepo] = useState<OpenSourceRepo | null>(null);
const [repoUrlCopied, setRepoUrlCopied] = useState(false);
const [page, setPage] = useState(1);
const [showSuggestModal, setShowSuggestModal] = useState(false);
const { user } = useAuthStore();
Expand Down Expand Up @@ -101,6 +104,17 @@ export default function RepoDiscoveryPage() {
setPage(1);
};

const copySelectedRepoUrl = () => {
if (!selectedRepo) return;
navigator.clipboard.writeText(selectedRepo.url);
setRepoUrlCopied(true);
setTimeout(() => setRepoUrlCopied(false), 1500);
};
Comment on lines +107 to +112
Comment on lines +107 to +112
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

MDN Web Clipboard API: does navigator.clipboard.writeText return a Promise that can reject (e.g., permission denied / insecure context), and what failure handling is recommended?

💡 Result:

Yes, navigator.clipboard.writeText returns a Promise that can reject [1][2]. If the operation fails—such as due to permission denial, lack of a secure context (HTTPS), or the document losing focus—the Promise will reject, throwing an error [1][3][4][2]. Recommended failure handling includes: 1. Always use try...catch blocks: Because the Promise rejects silently if unhandled, you should wrap calls in a try...catch block to manage errors and provide user feedback [1][3][4]. 2. Verify context and state: Ensure the call occurs within a secure context (window.isSecureContext) and that the document is focused (document.hasFocus) before attempting the operation [4][5]. 3. Trigger from user interaction: To satisfy browser security requirements (transient user activation), call writeText directly within a synchronous event handler, such as a click listener [3][6]. Performing asynchronous operations between the user gesture and the clipboard call can cause the gesture context to be lost, particularly in some browsers like Safari [3]. 4. Consider a fallback: While document.execCommand('copy') is deprecated, it remains a common fallback for non-secure contexts or environments where the Clipboard API is unsupported or restricted, though its reliability is decreasing [5]. Example implementation: async function writeClipboardText(text) { try { await navigator.clipboard.writeText(text); console.log('Text copied to clipboard'); } catch (err) { console.error('Failed to copy: ', err); } }

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="client/src/module/student/opensource/RepoDiscoveryPage.tsx"
if [ ! -f "$FILE" ]; then
  echo "Missing $FILE" >&2
  exit 1
fi
echo "=== copySelectedRepoUrl ==="
rg -n "copySelectedRepoUrl|writeText\\(" "$FILE" || true
echo
echo "=== Around copy handler (approx. lines 90-140) ==="
nl -ba "$FILE" | sed -n '90,150p'
echo
echo "=== Around tooltip / modal actions (approx. lines 560-700) ==="
nl -ba "$FILE" | sed -n '560,700'

Repository: Sachinchaurasiya360/InternHack

Length of output: 369


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="client/src/module/student/opensource/RepoDiscoveryPage.tsx"

echo "=== copySelectedRepoUrl & reset (lines 95-140) ==="
cat -n "$FILE" | sed -n '95,150p'

echo
echo "=== Search for raw <button> usage and tooltip text-[...] ==="
rg -n "<button\b|text-\[[0-9]+px\]" "$FILE"

echo
echo "=== Around occurrences (line 580-700) ==="
cat -n "$FILE" | sed -n '580,700p'

Repository: Sachinchaurasiya360/InternHack

Length of output: 10023


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="client/src/module/student/opensource/RepoDiscoveryPage.tsx"

echo "=== Imports (top of file) ==="
sed -n '1,90p' "$FILE"

echo
echo "=== Search for Button component usage/import ==="
rg -n "components/ui/button|\\bButton\\b" "$FILE" || true

echo
echo "=== Search for useRef/timeout patterns ==="
rg -n "\\buseRef\\b|setTimeout\\(" "$FILE"

Repository: Sachinchaurasiya360/InternHack

Length of output: 3981


Fix clipboard copy handling and align copy control UI to guidelines (client/src/module/student/opensource/RepoDiscoveryPage.tsx)

  • Lines 107-112: navigator.clipboard.writeText(...) is async and can reject; also overlapping setTimeouts can clear the “Copied!” state too early—debounce/cancel the previous timer and handle failures.
  • Lines 619-636: the copy action uses a raw <button> (should use the shared Button component), and the “Copied!” tooltip uses text-[10px] (avoid arbitrary bracket text sizes).
💡 Suggested fix (clipboard + debounced copied timer)
+  const copyResetTimerRef = useRef<number | null>(null);
+
   const copySelectedRepoUrl = () => {
     if (!selectedRepo) return;
-    navigator.clipboard.writeText(selectedRepo.url);
-    setRepoUrlCopied(true);
-    setTimeout(() => setRepoUrlCopied(false), 1500);
+    navigator.clipboard
+      .writeText(selectedRepo.url)
+      .then(() => {
+        setRepoUrlCopied(true);
+        if (copyResetTimerRef.current) {
+          window.clearTimeout(copyResetTimerRef.current);
+        }
+        copyResetTimerRef.current = window.setTimeout(() => {
+          setRepoUrlCopied(false);
+          copyResetTimerRef.current = null;
+        }, 1500);
+      })
+      .catch(() => {
+        setRepoUrlCopied(false);
+      });
   };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const copySelectedRepoUrl = () => {
if (!selectedRepo) return;
navigator.clipboard.writeText(selectedRepo.url);
setRepoUrlCopied(true);
setTimeout(() => setRepoUrlCopied(false), 1500);
};
const copyResetTimerRef = useRef<number | null>(null);
const copySelectedRepoUrl = () => {
if (!selectedRepo) return;
navigator.clipboard
.writeText(selectedRepo.url)
.then(() => {
setRepoUrlCopied(true);
if (copyResetTimerRef.current) {
window.clearTimeout(copyResetTimerRef.current);
}
copyResetTimerRef.current = window.setTimeout(() => {
setRepoUrlCopied(false);
copyResetTimerRef.current = null;
}, 1500);
})
.catch(() => {
setRepoUrlCopied(false);
});
};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@client/src/module/student/opensource/RepoDiscoveryPage.tsx` around lines 107
- 112, The copySelectedRepoUrl handler needs to await
navigator.clipboard.writeText and handle rejections, only setting
setRepoUrlCopied(true) on success; add a persistent timer ref (e.g.,
repoCopyTimerRef) and clearTimeout(repoCopyTimerRef.current) before setting a
new timeout to reset setRepoUrlCopied(false) to avoid overlapping timers; also
update the copy control UI to use the shared Button component instead of a raw
<button> (replace the element used in the copy action around lines 619-636) and
change the “Copied!” tooltip/label to use a theme-safe small text class (avoid
text-[10px], use an existing small text token such as text-sm or a design-system
utility).


useEffect(() => {
setRepoUrlCopied(false);
}, [selectedRepo]);

const activeFilters =
(selectedDomain !== "ALL" ? 1 : 0) +
(selectedDifficulty !== "ALL" ? 1 : 0);
Expand Down Expand Up @@ -591,16 +605,36 @@ export default function RepoDiscoveryPage() {
</div>
</div>

{/* View on GitHub */}
<a
href={selectedRepo.url}
target="_blank"
rel="noopener noreferrer"
className="group flex items-center justify-center gap-2 w-full py-3 rounded-md bg-lime-400 hover:bg-lime-300 text-stone-950 text-sm font-bold transition-colors no-underline"
>
View on GitHub
<ExternalLink className="w-4 h-4 transition-transform group-hover:translate-x-0.5" />
</a>
{/* Repository actions */}
<div className="flex items-center gap-2">
<a
href={selectedRepo.url}
target="_blank"
rel="noopener noreferrer"
className="group flex flex-1 items-center justify-center gap-2 py-3 rounded-md bg-lime-400 hover:bg-lime-300 text-stone-950 text-sm font-bold transition-colors no-underline"
>
View on GitHub
<ExternalLink className="w-4 h-4 transition-transform group-hover:translate-x-0.5" />
</a>
<button
type="button"
onClick={copySelectedRepoUrl}
className="relative flex h-11 w-11 shrink-0 items-center justify-center rounded-md border border-stone-200 dark:border-white/10 bg-white dark:bg-stone-900 text-stone-700 dark:text-stone-300 transition-colors hover:bg-stone-100 dark:hover:bg-white/5 cursor-pointer"
aria-label={repoUrlCopied ? "Copied repository URL" : "Copy repository URL"}
Comment on lines +619 to +623
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Use the shared Button component for the new copy action.

This new action is introduced as a raw <button> instead of the reusable Button component used for standardized variants/sizes/modes.

As per coding guidelines, "Use the reusable Button component from client/src/components/ui/button.tsx for all new buttons with variants (primary, secondary, mono, ghost, danger), modes (button, icon, link), and sizes (sm, md, lg)".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@client/src/module/student/opensource/RepoDiscoveryPage.tsx` around lines 619
- 623, Replace the raw <button> in RepoDiscoveryPage with the shared Button
component: import the shared Button from the UI button module, pass
onClick={copySelectedRepoUrl} and the aria-label (use repoUrlCopied to toggle
the label), and convert the className styling into the Button's props (e.g.,
mode="icon" and an appropriate size and variant like size="md" variant="ghost")
while rendering the same icon/children; ensure the Button receives any needed
accessibility attributes and retains the hover/focus behavior.

title={repoUrlCopied ? "Copied!" : "Copy repository URL"}
>
{repoUrlCopied ? (
<Check className="w-4 h-4 text-green-500" />
) : (
<Copy className="w-4 h-4" />
)}
{repoUrlCopied && (
<span className="absolute -top-8 right-0 rounded-md bg-stone-900 dark:bg-stone-50 px-2 py-1 text-[10px] font-bold text-white dark:text-stone-950 shadow-lg">
Copied!
</span>
)}
</button>
</div>
</div>
</motion.div>
</motion.div>
Expand Down