refactor(frontend): implement copyToClipboard utility for consistent clipboard operations across components

This commit is contained in:
Viren070
2025-10-29 12:55:23 +00:00
parent b1ad69decf
commit 6867809710
6 changed files with 83 additions and 55 deletions
@@ -11,6 +11,7 @@ import { Toaster } from '@/components/ui/toaster';
import { toast } from 'sonner';
import { UserConfigAPI } from '@/services/api';
import { BiCopy } from 'react-icons/bi';
import { copyToClipboard } from '@/utils/clipboard';
function OAuthCallbackContent() {
const [code, setCode] = useState<string | null>(null);
@@ -45,17 +46,12 @@ function OAuthCallbackContent() {
const handleCopy = async () => {
if (code) {
try {
await navigator.clipboard.writeText(code);
toast.success('Copied!', {
description:
'The authorization code has been copied to your clipboard.',
});
} catch (err) {
toast.error('Failed to copy', {
description: 'Please try copying the code manually.',
});
}
await copyToClipboard(code, {
successMessage: 'Copied!',
errorMessage: 'Failed to copy',
description:
'The authorization code has been copied to your clipboard.',
});
}
};
@@ -84,6 +84,7 @@ import { TbFilterCode } from 'react-icons/tb';
import { PasswordInput } from '../ui/password-input';
import MarkdownLite from '../shared/markdown-lite';
import { useMode } from '@/context/mode';
import { copyToClipboard } from '@/utils/clipboard';
type Resolution = (typeof RESOLUTIONS)[number];
type Quality = (typeof QUALITIES)[number];
@@ -2590,10 +2591,11 @@ function Content() {
size="sm"
intent="primary-subtle"
icon={<FaRegCopy />}
onClick={() => {
navigator.clipboard.writeText(url);
toast.success('URL copied to clipboard');
}}
onClick={() =>
copyToClipboard(url, {
successMessage: 'URL copied to clipboard',
})
}
/>
</div>
)
@@ -24,6 +24,7 @@ import { Tooltip } from '../ui/tooltip';
import { FaFileImport, FaFileExport } from 'react-icons/fa';
import { IconButton } from '../ui/button';
import { ImportModal } from '../shared/import-modal';
import { copyToClipboard } from '@/utils/clipboard';
const formatterChoices = Object.values(constants.FORMATTER_DETAILS);
// Remove the throttle utility and replace with FormatQueue
@@ -614,22 +615,10 @@ function SnippetsButton() {
intent="primary-outline"
className="sm:ml-4 flex-shrink-0"
onClick={async () => {
if (!navigator.clipboard) {
toast.error(
'The clipboard API is not available in this browser or context.'
);
return;
}
try {
await navigator.clipboard.writeText(snippet.value);
toast.success('Snippet copied to clipboard');
} catch (error) {
console.error(
'Failed to copy snippet to clipboard:',
error
);
toast.error('Failed to copy snippet to clipboard');
}
await copyToClipboard(snippet.value, {
successMessage: 'Snippet copied to clipboard',
errorMessage: 'Failed to copy snippet to clipboard',
});
}}
title="Copy snippet"
>
@@ -11,6 +11,7 @@ import { toast } from 'sonner';
import { CopyIcon, DownloadIcon, PlusIcon, UploadIcon } from 'lucide-react';
import { useStatus } from '@/context/status';
import { BiCopy } from 'react-icons/bi';
import { copyToClipboard } from '@/utils/clipboard';
import { PageControls } from '../shared/page-controls';
import { useDisclosure } from '@/hooks/disclosure';
import { Modal } from '../ui/modal';
@@ -304,18 +305,10 @@ function Content() {
const encodedManifest = encodeURIComponent(manifestUrl);
const copyManifestUrl = async () => {
try {
if (!navigator.clipboard) {
toast.error(
'The Clipboard API is not supported on this browser or context, please manually copy the URL'
);
return;
}
await navigator.clipboard.writeText(manifestUrl);
toast.success('Manifest URL copied to clipboard');
} catch (err) {
toast.error('Failed to copy manifest URL');
}
await copyToClipboard(manifestUrl, {
successMessage: 'Manifest URL copied to clipboard',
errorMessage: 'Failed to copy manifest URL',
});
};
const handleDelete = async () => {
@@ -431,10 +424,11 @@ function Content() {
</span>
<BiCopy
className="min-h-5 min-w-5 cursor-pointer"
onClick={() => {
navigator.clipboard.writeText(uuid);
toast.success('UUID copied to clipboard');
}}
onClick={() =>
copyToClipboard(uuid, {
successMessage: 'UUID copied to clipboard',
})
}
/>
</div>
<p className="text-sm text-[--muted]">
@@ -1,5 +1,6 @@
import React from 'react';
import { toast } from 'sonner';
import { copyToClipboard } from '@/utils/clipboard';
interface MarkdownLiteProps {
children: string;
@@ -120,14 +121,10 @@ function processInlineMarkdown(text: string, stopPropagation: boolean) {
if (stopPropagation) {
e.stopPropagation();
}
// copy to clipboard
try {
await navigator.clipboard.writeText(match.slice(1, -1));
toast.success('Copied to clipboard');
} catch (error) {
console.error('Failed to copy to clipboard:', error);
toast.error('Failed to copy to clipboard');
}
await copyToClipboard(match.slice(1, -1), {
successMessage: 'Copied to clipboard',
errorMessage: 'Failed to copy to clipboard',
});
}}
className="bg-muted px-1 py-0.5 rounded text-[--brand] font-mono text-xs break-all cursor-pointer"
>
+50
View File
@@ -0,0 +1,50 @@
import { toast } from 'sonner';
type CopyOptions = {
successMessage?: string;
errorMessage?: string;
description?: string;
};
export async function copyToClipboard(text: string, options: CopyOptions = {}) {
const {
successMessage = 'Copied to clipboard',
errorMessage = 'Failed to copy to clipboard',
description,
} = options;
try {
if (typeof navigator === 'undefined') {
throw new Error('Navigator is not available');
}
if (navigator.clipboard && navigator.clipboard.writeText) {
await navigator.clipboard.writeText(text);
toast.success(successMessage, description ? { description } : undefined);
return { success: true as const };
}
// Fallback for environments where Clipboard API is unavailable (e.g., http:, some iframes)
const textarea = document.createElement('textarea');
textarea.value = text;
// Prevent scrolling to bottom on iOS
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.focus();
textarea.select();
const successful = document.execCommand('copy');
document.body.removeChild(textarea);
if (!successful) {
throw new Error('execCommand copy failed');
}
toast.success(successMessage, description ? { description } : undefined);
return { success: true as const };
} catch (error) {
toast.error(errorMessage);
return { success: false as const, error: error as unknown };
}
}