b119a52e08
* Adds a new route for logging in with mfa * New path for security page * Adds “Security” link to account side menu * Update the Switch component to allow label positions left and right * Optionally hide the Close button in the Dialog title bar * Installs `qrcode` react package for generating QR codes. * CopyButton component now takes children * New Security route for setting up MFA * Adds new OTP package for the chadcn InputOTP component * Adds new InputOTP chadcn component * Adds InputOTP chadcn component to the MFA login screen * InputOTP component supports variant styles * Improvements to form handling * Show a confirmation modal before you can disable MFA * Revert redirect back to the dashboard for now * Implement MFA enabling and disabling * Refactor and cleanup mfa management code * More cleanup * Handle errors in the management action * Implement mfa login flow * recovery code input should be password * Implement rate limiting on the mfa validation endpoint * Better error ux * Implement mfa emails and apply James' updates * Use latest @better-auth/utils * Improvements via CodeRabbit review --------- Co-authored-by: James Ritchie <james@trigger.dev>
106 lines
2.6 KiB
TypeScript
106 lines
2.6 KiB
TypeScript
import { ClipboardCheckIcon, ClipboardIcon } from "lucide-react";
|
|
import { useCopy } from "~/hooks/useCopy";
|
|
import { cn } from "~/utils/cn";
|
|
import { Button } from "./Buttons";
|
|
import { SimpleTooltip } from "./Tooltip";
|
|
|
|
const sizes = {
|
|
"extra-small": {
|
|
icon: "size-3",
|
|
button: "h-5 px-1",
|
|
},
|
|
small: {
|
|
icon: "size-3.5",
|
|
button: "h-6 px-1",
|
|
},
|
|
medium: {
|
|
icon: "size-4",
|
|
button: "h-8 px-1.5",
|
|
},
|
|
};
|
|
|
|
type CopyButtonProps = {
|
|
value: string;
|
|
variant?: "icon" | "button";
|
|
size?: keyof typeof sizes;
|
|
className?: string;
|
|
buttonClassName?: string;
|
|
showTooltip?: boolean;
|
|
buttonVariant?: "primary" | "secondary" | "tertiary" | "minimal";
|
|
children?: React.ReactNode;
|
|
};
|
|
|
|
export function CopyButton({
|
|
value,
|
|
variant = "button",
|
|
size = "medium",
|
|
className,
|
|
buttonClassName,
|
|
showTooltip = true,
|
|
buttonVariant = "tertiary",
|
|
children,
|
|
}: CopyButtonProps) {
|
|
const { copy, copied } = useCopy(value);
|
|
|
|
const { icon: iconSize, button: buttonSize } = sizes[size];
|
|
|
|
const button =
|
|
variant === "icon" ? (
|
|
<span
|
|
onClick={copy}
|
|
className={cn(
|
|
buttonSize,
|
|
"flex items-center justify-center rounded border border-charcoal-650 bg-charcoal-750",
|
|
copied
|
|
? "text-green-500"
|
|
: "text-text-dimmed hover:border-charcoal-600 hover:bg-charcoal-700 hover:text-text-bright",
|
|
buttonClassName
|
|
)}
|
|
>
|
|
{copied ? (
|
|
<ClipboardCheckIcon className={iconSize} />
|
|
) : (
|
|
<ClipboardIcon className={iconSize} />
|
|
)}
|
|
</span>
|
|
) : (
|
|
<Button
|
|
variant={`${buttonVariant}/${size === "extra-small" ? "small" : size}`}
|
|
onClick={copy}
|
|
className={cn("shrink-0", buttonClassName)}
|
|
LeadingIcon={
|
|
copied ? (
|
|
<ClipboardCheckIcon
|
|
className={cn(
|
|
iconSize,
|
|
buttonVariant === "primary" ? "text-background-dimmed" : "text-green-500"
|
|
)}
|
|
/>
|
|
) : (
|
|
<ClipboardIcon
|
|
className={cn(
|
|
iconSize,
|
|
buttonVariant === "primary" ? "text-background-dimmed" : "text-text-dimmed"
|
|
)}
|
|
/>
|
|
)
|
|
}
|
|
>
|
|
{children}
|
|
</Button>
|
|
);
|
|
|
|
if (!showTooltip) return <span className={className}>{button}</span>;
|
|
|
|
return (
|
|
<span className={className}>
|
|
<SimpleTooltip
|
|
button={button}
|
|
content={copied ? "Copied!" : "Copy"}
|
|
className="font-sans"
|
|
disableHoverableContent
|
|
/>
|
|
</span>
|
|
);
|
|
}
|