mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 13:01:00 +00:00
fix(admin): truncate and copy the login-overview IP address
A long IPv6 in the login overview forced the table to scroll sideways and the full address was awkward to lift out. Cap the cell, ellipsize the address, and make it a button that copies the full value to the clipboard with a transient checkmark and a toast — keyboard-activatable, with an accessible label. The empty case stays plain text.
This commit is contained in:
1 parent
45ca6ff24b
commit
2ed73a3a34
7 files changed
+75
-7
No files matched your search
@@ -2,8 +2,11 @@
|
||||
|
||||
import { useMemo, useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
Check,
|
||||
CheckCircle2,
|
||||
Copy,
|
||||
Download,
|
||||
Loader2,
|
||||
MapPin,
|
||||
@@ -94,6 +97,25 @@ export function LoginOverviewSection() {
|
||||
const AUTH_ACTION_LABELS = useAuthActionLabels();
|
||||
const AUTH_PROVIDER_LABELS = useAuthProviderLabels();
|
||||
|
||||
// v1.25.11 (#151) — long IPv6 addresses are truncated in the table cell so
|
||||
// they never force the wrapper into horizontal scroll; the full value is one
|
||||
// click away. Track the most-recently-copied row so the icon can flip to a
|
||||
// checkmark for transient feedback.
|
||||
const [copiedIp, setCopiedIp] = useState<string | null>(null);
|
||||
|
||||
async function copyIp(id: string, ip: string) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(ip);
|
||||
setCopiedIp(id);
|
||||
toast.success(t("admin.ipCopied"));
|
||||
window.setTimeout(() => {
|
||||
setCopiedIp((current) => (current === id ? null : current));
|
||||
}, 2_000);
|
||||
} catch {
|
||||
toast.error(t("admin.copyFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
// Build the query string once so it's reused by the data-query key, the
|
||||
// export download, and the next-/prev- buttons.
|
||||
const queryParams = useMemo(() => {
|
||||
@@ -464,7 +486,35 @@ export function LoginOverviewSection() {
|
||||
</span>
|
||||
</td>
|
||||
<td className="text-muted-foreground px-3 py-2 font-mono text-xs">
|
||||
{entry.ipAddress ?? "—"}
|
||||
{entry.ipAddress ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
copyIp(entry.id, entry.ipAddress as string)
|
||||
}
|
||||
title={entry.ipAddress}
|
||||
aria-label={t("admin.copyIp")}
|
||||
data-slot="login-overview-ip"
|
||||
className="hover:text-foreground focus-visible:ring-ring inline-flex max-w-[10rem] items-center gap-1 rounded font-mono focus-visible:ring-2 focus-visible:outline-none"
|
||||
>
|
||||
<span className="truncate">
|
||||
{entry.ipAddress}
|
||||
</span>
|
||||
{copiedIp === entry.id ? (
|
||||
<Check
|
||||
className="text-dracula-green h-3 w-3 shrink-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
) : (
|
||||
<Copy
|
||||
className="h-3 w-3 shrink-0 opacity-60"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
) : (
|
||||
"—"
|
||||
)}
|
||||
</td>
|
||||
<td className="text-muted-foreground px-3 py-2 text-xs">
|
||||
{entry.location ?? "—"}
|
||||
|
||||
Reference in new issue
Block a user