feat: add copy to clipboard functionality for MAC address in network settings

This commit is contained in:
Adam Shiervani 2025-10-13 17:00:43 +02:00
parent 76d256b69a
commit 5e06625966
3 changed files with 73 additions and 16 deletions

4
ui/package-lock.json generated
View File

@ -28,6 +28,7 @@
"react": "^19.1.1",
"react-animate-height": "^3.2.3",
"react-dom": "^19.1.1",
"react-hook-form": "^7.62.0",
"react-hot-toast": "^2.6.0",
"react-icons": "^5.5.0",
"react-router": "^7.9.3",
@ -5856,8 +5857,6 @@
"react": "^19.1.1"
}
},
<<<<<<< Updated upstream
=======
"node_modules/react-hook-form": {
"version": "7.62.0",
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.62.0.tgz",
@ -5874,7 +5873,6 @@
"react": "^16.8.0 || ^17 || ^18 || ^19"
}
},
>>>>>>> Stashed changes
"node_modules/react-hot-toast": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.6.0.tgz",

View File

@ -0,0 +1,49 @@
import { useCallback, useState } from "react";
export function useCopyToClipboard(resetInterval = 2000) {
const [isCopied, setIsCopied] = useState(false);
const copy = useCallback(async (text: string) => {
if (!text) return false;
let success = false;
if (navigator.clipboard && window.isSecureContext) {
try {
await navigator.clipboard.writeText(text);
success = true;
} catch (err) {
console.warn("Clipboard API failed:", err);
}
}
// Fallback for insecure contexts
if (!success) {
const textarea = document.createElement("textarea");
textarea.value = text;
textarea.style.position = "fixed";
textarea.style.opacity = "0";
document.body.appendChild(textarea);
textarea.focus();
textarea.select();
try {
success = document.execCommand("copy");
} catch (err) {
console.error("Fallback copy failed:", err);
success = false;
} finally {
document.body.removeChild(textarea);
}
}
setIsCopied(success);
if (success && resetInterval > 0) {
setTimeout(() => setIsCopied(false), resetInterval);
}
return success;
}, [resetInterval]);
return { copy, isCopied };
}

View File

@ -2,7 +2,7 @@ import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
import { useCallback, useEffect, useRef, useState } from "react";
import { FieldValues, FormProvider, useForm } from "react-hook-form";
import { LuEthernetPort } from "react-icons/lu";
import { LuCopy, LuEthernetPort } from "react-icons/lu";
import validator from "validator";
import { ConfirmDialog } from "@/components/ConfirmDialog";
@ -24,6 +24,7 @@ import StaticIpv4Card from "../components/StaticIpv4Card";
import StaticIpv6Card from "../components/StaticIpv6Card";
import { useJsonRpc } from "../hooks/useJsonRpc";
import { SettingsItem } from "../components/SettingsItem";
import { useCopyToClipboard } from "../components/useCopyToClipBoard";
dayjs.extend(relativeTime);
@ -225,6 +226,8 @@ export default function SettingsNetworkRoute() {
});
};
const { copy } = useCopyToClipboard();
return (
<>
<FormProvider {...formMethods}>
@ -248,19 +251,26 @@ export default function SettingsNetworkRoute() {
}
/>
<div className="space-y-4">
<SettingsItem
title="MAC Address"
description="Hardware identifier for the network interface"
>
<InputField
type="text"
size="SM"
value={networkState?.mac_address}
error={""}
readOnly={true}
className="dark:!text-opacity-60"
<div className="flex items-center justify-between">
<SettingsItem
title="MAC Address"
description="Hardware identifier for the network interface"
/>
</SettingsItem>
<div className="flex items-center">
<GridCard cardClassName="rounded-r-none">
<div className=" h-[34px] flex items-center text-xs select-all text-black font-mono dark:text-white px-3 ">
{networkState?.mac_address} {" "}
</div>
</GridCard>
<Button className="rounded-l-none border-l-blue-900 dark:border-l-blue-600" size="SM" type="button" theme="primary" LeadingIcon={LuCopy} onClick={async () => {
if (await copy(networkState?.mac_address || "")) {
notifications.success("MAC address copied to clipboard");
} else {
notifications.error("Failed to copy MAC address");
}
}} />
</div>
</div>
<SettingsItem title="Hostname" description="Set the device hostname">
<InputField
size="SM"