refactor: remove copy to clipboard everywhere as it's not possible on http
This commit is contained in:
parent
9c5f7d6516
commit
6a696d090e
5 changed files with 8 additions and 109 deletions
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in a new issue