refactor: remove copy to clipboard everywhere as it's not possible on http

This commit is contained in:
Nicolas Meienberger 2026-01-09 20:40:01 +01:00
parent 9c5f7d6516
commit 6a696d090e
5 changed files with 8 additions and 109 deletions

View file

@ -1,6 +1,4 @@
import type React from "react"; import type React from "react";
import { toast } from "sonner";
import { copyToClipboard } from "~/utils/clipboard";
interface CodeBlockProps { interface CodeBlockProps {
code: string; code: string;
@ -9,11 +7,6 @@ interface CodeBlockProps {
} }
export const CodeBlock: React.FC<CodeBlockProps> = ({ code, filename }) => { export const CodeBlock: React.FC<CodeBlockProps> = ({ code, filename }) => {
const handleCopy = async () => {
await copyToClipboard(code);
toast.success("Code copied to clipboard");
};
return ( return (
<div className="overflow-hidden rounded-sm bg-card-header ring-1 ring-white/10"> <div className="overflow-hidden rounded-sm bg-card-header ring-1 ring-white/10">
<div className="flex items-center justify-between border-b border-white/10 px-4 py-2 text-xs"> <div className="flex items-center justify-between border-b border-white/10 px-4 py-2 text-xs">
@ -23,16 +16,9 @@ export const CodeBlock: React.FC<CodeBlockProps> = ({ code, filename }) => {
<span className="h-2.5 w-2.5 rounded-full bg-emerald-500" /> <span className="h-2.5 w-2.5 rounded-full bg-emerald-500" />
{filename && <span className="ml-3 font-medium">{filename}</span>} {filename && <span className="ml-3 font-medium">{filename}</span>}
</div> </div>
<button
type="button"
onClick={() => handleCopy()}
className="cursor-pointer rounded-md bg-white/5 px-2 py-1 text-[11px] font-medium ring-1 ring-inset ring-white/10 transition hover:bg-white/10 active:translate-y-px"
>
Copy
</button>
</div> </div>
<pre className="text-xs m-0 px-4 py-2 bg-card-header"> <pre className="text-xs m-0 px-4 py-2 bg-card-header">
<code className="text-white/80">{code}</code> <code className="text-white/80 select-all">{code}</code>
</pre> </pre>
</div> </div>
); );

View file

@ -1,7 +1,4 @@
import { toast } from "sonner";
import { Button } from "~/client/components/ui/button";
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "~/client/components/ui/dialog"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "~/client/components/ui/dialog";
import { copyToClipboard } from "~/utils/clipboard";
const RESET_PASSWORD_COMMAND = "docker exec -it zerobyte bun run cli reset-password"; const RESET_PASSWORD_COMMAND = "docker exec -it zerobyte bun run cli reset-password";
@ -11,11 +8,6 @@ type ResetPasswordDialogProps = {
}; };
export const ResetPasswordDialog = ({ open, onOpenChange }: ResetPasswordDialogProps) => { export const ResetPasswordDialog = ({ open, onOpenChange }: ResetPasswordDialogProps) => {
const handleCopy = async () => {
await copyToClipboard(RESET_PASSWORD_COMMAND);
toast.success("Command copied to clipboard");
};
return ( return (
<Dialog open={open} onOpenChange={onOpenChange}> <Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-xl"> <DialogContent className="sm:max-w-xl">
@ -26,13 +18,10 @@ export const ResetPasswordDialog = ({ open, onOpenChange }: ResetPasswordDialogP
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<div className="space-y-4"> <div className="space-y-4">
<div className="rounded-md bg-muted p-4 font-mono text-sm break-all">{RESET_PASSWORD_COMMAND}</div> <div className="rounded-md bg-muted p-4 font-mono text-sm break-all select-all">{RESET_PASSWORD_COMMAND}</div>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
This command will start an interactive session where you can enter a new password for your account. This command will start an interactive session where you can enter a new password for your account.
</p> </p>
<Button onClick={handleCopy} variant="outline" className="w-full">
Copy Command
</Button>
</div> </div>
</DialogContent> </DialogContent>
</Dialog> </Dialog>

View file

@ -1,6 +1,6 @@
import { useState } from "react"; import { useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { Copy, RefreshCw } from "lucide-react"; import { RefreshCw } from "lucide-react";
import { Button } from "~/client/components/ui/button"; import { Button } from "~/client/components/ui/button";
import { import {
Dialog, Dialog,
@ -13,7 +13,6 @@ import {
import { Input } from "~/client/components/ui/input"; import { Input } from "~/client/components/ui/input";
import { Label } from "~/client/components/ui/label"; import { Label } from "~/client/components/ui/label";
import { authClient } from "~/client/lib/auth-client"; import { authClient } from "~/client/lib/auth-client";
import { copyToClipboard } from "~/utils/clipboard";
type BackupCodesDialogProps = { type BackupCodesDialogProps = {
open: boolean; open: boolean;
@ -64,11 +63,6 @@ export const BackupCodesDialog = ({ open, onOpenChange }: BackupCodesDialogProps
}, 200); }, 200);
}; };
const copyAllBackupCodes = () => {
const text = backupCodes.join("\n");
void copyToClipboard(text);
};
return ( return (
<Dialog open={open} onOpenChange={handleClose}> <Dialog open={open} onOpenChange={handleClose}>
<DialogContent> <DialogContent>
@ -84,24 +78,11 @@ export const BackupCodesDialog = ({ open, onOpenChange }: BackupCodesDialogProps
<> <>
<div className="p-3 bg-muted rounded-md space-y-1 max-h-48 overflow-y-auto"> <div className="p-3 bg-muted rounded-md space-y-1 max-h-48 overflow-y-auto">
{backupCodes.map((code) => ( {backupCodes.map((code) => (
<div key={code} className="text-sm font-mono flex items-center justify-between"> <div key={code} className="text-sm font-mono py-1">
<span>{code}</span> <span className="select-all block w-full">{code}</span>
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => copyToClipboard(code)}
className="h-8 w-8 p-0"
>
<Copy className="h-4 w-4" />
</Button>
</div> </div>
))} ))}
</div> </div>
<Button type="button" variant="outline" onClick={copyAllBackupCodes} className="w-full">
<Copy className="h-4 w-4 mr-2" />
Copy all
</Button>
</> </>
) : ( ) : (
<form onSubmit={handleGenerate} className="space-y-4"> <form onSubmit={handleGenerate} className="space-y-4">

View file

@ -1,6 +1,5 @@
import { useState } from "react"; import { useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { Copy } from "lucide-react";
import { QRCodeCanvas } from "qrcode.react"; import { QRCodeCanvas } from "qrcode.react";
import { Button } from "~/client/components/ui/button"; import { Button } from "~/client/components/ui/button";
import { import {
@ -15,7 +14,6 @@ import { Input } from "~/client/components/ui/input";
import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from "~/client/components/ui/input-otp"; import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from "~/client/components/ui/input-otp";
import { Label } from "~/client/components/ui/label"; import { Label } from "~/client/components/ui/label";
import { authClient } from "~/client/lib/auth-client"; import { authClient } from "~/client/lib/auth-client";
import { copyToClipboard } from "~/utils/clipboard";
type TwoFactorSetupDialogProps = { type TwoFactorSetupDialogProps = {
open: boolean; open: boolean;
@ -107,11 +105,6 @@ export const TwoFactorSetupDialog = ({ open, onOpenChange, onSuccess }: TwoFacto
}, 200); }, 200);
}; };
const copyAllBackupCodes = () => {
const text = backupCodes.join("\n");
void copyToClipboard(text);
};
return ( return (
<Dialog open={open} onOpenChange={handleClose}> <Dialog open={open} onOpenChange={handleClose}>
<DialogContent className="max-w-md"> <DialogContent className="max-w-md">
@ -165,16 +158,8 @@ export const TwoFactorSetupDialog = ({ open, onOpenChange, onSuccess }: TwoFacto
<Input <Input
value={totpUri.split("secret=")[1]?.split("&")[0] || ""} value={totpUri.split("secret=")[1]?.split("&")[0] || ""}
readOnly readOnly
className="text-xs font-mono" className="text-xs font-mono select-all"
/> />
<Button
type="button"
variant="outline"
size="sm"
onClick={() => copyToClipboard(totpUri.split("secret=")[1]?.split("&")[0] || "")}
>
<Copy className="h-4 w-4" />
</Button>
</div> </div>
</div> </div>
{backupCodes.length > 0 && ( {backupCodes.length > 0 && (
@ -182,24 +167,11 @@ export const TwoFactorSetupDialog = ({ open, onOpenChange, onSuccess }: TwoFacto
<Label className="text-xs">Backup codes (save these securely)</Label> <Label className="text-xs">Backup codes (save these securely)</Label>
<div className="p-3 bg-muted rounded-md space-y-1 max-h-32 overflow-y-auto"> <div className="p-3 bg-muted rounded-md space-y-1 max-h-32 overflow-y-auto">
{backupCodes.map((code) => ( {backupCodes.map((code) => (
<div key={code} className="text-xs font-mono flex items-center justify-between"> <div key={code} className="text-xs font-mono py-1">
<span>{code}</span> <span className="select-all block w-full">{code}</span>
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => copyToClipboard(code)}
className="h-6 w-6 p-0"
>
<Copy className="h-3 w-3" />
</Button>
</div> </div>
))} ))}
</div> </div>
<Button type="button" variant="outline" size="sm" onClick={copyAllBackupCodes} className="w-full">
<Copy className="h-4 w-4 mr-2" />
Copy all
</Button>
</div> </div>
)} )}
</div> </div>

View file

@ -1,29 +0,0 @@
import { toast } from "sonner";
export async function copyToClipboard(textToCopy: string) {
// Navigator clipboard api needs a secure context (https)
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(textToCopy);
toast.success("Copied to clipboard");
} else {
// Use the 'out of viewport hidden text area' trick
const textArea = document.createElement("textarea");
textArea.value = textToCopy;
// Move textarea out of the viewport so it's not visible
textArea.style.position = "absolute";
textArea.style.left = "-999999px";
document.body.prepend(textArea);
textArea.select();
try {
document.execCommand("copy");
toast.success("Copied to clipboard");
} catch (error) {
console.error(error);
} finally {
textArea.remove();
}
}
}