fix: suppress date and button hydration warnings

This commit is contained in:
Aarnav Tale 2025-04-03 16:54:12 -04:00
parent 0ad578e651
commit f2747ada94
No known key found for this signature in database
7 changed files with 55 additions and 26 deletions

View File

@ -7,6 +7,7 @@ export interface AttributeProps {
value: string; value: string;
isCopyable?: boolean; isCopyable?: boolean;
link?: string; link?: string;
suppressHydrationWarning?: boolean;
} }
export default function Attribute({ export default function Attribute({
@ -14,6 +15,7 @@ export default function Attribute({
value, value,
link, link,
isCopyable, isCopyable,
suppressHydrationWarning,
}: AttributeProps) { }: AttributeProps) {
return ( return (
<dl className="flex items-center w-full gap-x-1"> <dl className="flex items-center w-full gap-x-1">
@ -27,6 +29,7 @@ export default function Attribute({
)} )}
</dt> </dt>
<dd <dd
suppressHydrationWarning={suppressHydrationWarning}
className={cn( className={cn(
'rounded-lg truncate w-full px-2.5 py-1 text-sm', 'rounded-lg truncate w-full px-2.5 py-1 text-sm',
'flex items-center gap-x-1', 'flex items-center gap-x-1',
@ -54,7 +57,12 @@ export default function Attribute({
}, 1000); }, 1000);
}} }}
> >
<p className="truncate">{value}</p> <p
suppressHydrationWarning={suppressHydrationWarning}
className="truncate"
>
{value}
</p>
<Check className="h-4.5 w-4.5 p-1 hidden data-[copied]:block" /> <Check className="h-4.5 w-4.5 p-1 hidden data-[copied]:block" />
<Copy className="h-4.5 w-4.5 p-1 block data-[copied]:hidden" /> <Copy className="h-4.5 w-4.5 p-1 block data-[copied]:hidden" />
</button> </button>

View File

@ -7,13 +7,13 @@ import {
} from 'react-router'; } from 'react-router';
import Button from '~/components/Button'; import Button from '~/components/Button';
import Card from '~/components/Card'; import Card from '~/components/Card';
import Code from '~/components/Code';
import Footer from '~/components/Footer'; import Footer from '~/components/Footer';
import Header from '~/components/Header'; import Header from '~/components/Header';
import type { LoadContext } from '~/server'; import type { LoadContext } from '~/server';
import { Capabilities } from '~/server/web/roles'; import { Capabilities } from '~/server/web/roles';
import { User } from '~/types'; import { User } from '~/types';
import log from '~/utils/log'; import log from '~/utils/log';
import toast from '~/utils/toast';
// This loads the bare minimum for the application to function // This loads the bare minimum for the application to function
// So we know that if context fails to load then well, oops? // So we know that if context fails to load then well, oops?
@ -122,7 +122,7 @@ export default function Shell() {
<> <>
<Header {...data} /> <Header {...data} />
{/* Always show the outlet if we are onboarding */} {/* Always show the outlet if we are onboarding */}
{(data.onboarding ? true : data.uiAccess) ? ( {(data.onboarding ? true : !data.uiAccess) ? (
<Outlet /> <Outlet />
) : ( ) : (
<Card className="mx-auto w-fit mt-24"> <Card className="mx-auto w-fit mt-24">
@ -134,11 +134,21 @@ export default function Shell() {
Connect to Tailscale with your devices to access this Tailnet. Use Connect to Tailscale with your devices to access this Tailnet. Use
this command to help you get started: this command to help you get started:
</Card.Text> </Card.Text>
<Button className="pointer-events-none text-md hover:bg-initial focus:ring-0"> <Button
<Code className="pointer-events-auto bg-transparent" isCopyable> className="flex text-md font-mono"
onPress={async () => {
await navigator.clipboard.writeText(
`tailscale up --login-server=${data.url}`,
);
toast('Copied to clipboard');
}}
>
tailscale up --login-server={data.url} tailscale up --login-server={data.url}
</Code>
</Button> </Button>
<p className="text-xs mt-1 opacity-50 text-center">
Click this button to copy the command.
</p>
<p className="mt-4 text-sm opacity-50"> <p className="mt-4 text-sm opacity-50">
Your account does not have access to the UI. Please contact your Your account does not have access to the UI. Please contact your
administrator if you believe this is a mistake. administrator if you believe this is a mistake.

View File

@ -180,7 +180,7 @@ export default function MachineRow({
isOnline={machine.online && !expired} isOnline={machine.online && !expired}
className="w-4 h-4" className="w-4 h-4"
/> />
<p> <p suppressHydrationWarning>
{machine.online && !expired {machine.online && !expired
? 'Connected' ? 'Connected'
: new Date(machine.lastSeen).toLocaleString()} : new Date(machine.lastSeen).toLocaleString()}

View File

@ -310,14 +310,17 @@ export default function Page() {
<Attribute name="Hostname" value={machine.name} /> <Attribute name="Hostname" value={machine.name} />
<Attribute isCopyable name="Node Key" value={machine.nodeKey} /> <Attribute isCopyable name="Node Key" value={machine.nodeKey} />
<Attribute <Attribute
suppressHydrationWarning
name="Created" name="Created"
value={new Date(machine.createdAt).toLocaleString()} value={new Date(machine.createdAt).toLocaleString()}
/> />
<Attribute <Attribute
suppressHydrationWarning
name="Last Seen" name="Last Seen"
value={new Date(machine.lastSeen).toLocaleString()} value={new Date(machine.lastSeen).toLocaleString()}
/> />
<Attribute <Attribute
suppressHydrationWarning
name="Expiry" name="Expiry"
value={expired ? new Date(machine.expiry).toLocaleString() : 'Never'} value={expired ? new Date(machine.expiry).toLocaleString() : 'Never'}
/> />

View File

@ -22,15 +22,19 @@ export default function AuthKeyRow({ authKey, server }: Props) {
<Attribute name="Reusable" value={authKey.reusable ? 'Yes' : 'No'} /> <Attribute name="Reusable" value={authKey.reusable ? 'Yes' : 'No'} />
<Attribute name="Ephemeral" value={authKey.ephemeral ? 'Yes' : 'No'} /> <Attribute name="Ephemeral" value={authKey.ephemeral ? 'Yes' : 'No'} />
<Attribute name="Used" value={authKey.used ? 'Yes' : 'No'} /> <Attribute name="Used" value={authKey.used ? 'Yes' : 'No'} />
<Attribute name="Created" value={createdAt} /> <Attribute suppressHydrationWarning name="Created" value={createdAt} />
<Attribute name="Expiration" value={expiration} /> <Attribute
suppressHydrationWarning
name="Expiration"
value={expiration}
/>
<p className="mb-1 mt-4"> <p className="mb-1 mt-4">
To use this key, run the following command on your device: To use this key, run the following command on your device:
</p> </p>
<Code className="text-sm"> <Code className="text-sm">
tailscale up --login-server {server} --authkey {authKey.key} tailscale up --login-server {server} --authkey {authKey.key}
</Code> </Code>
<div className="flex gap-4 items-center"> <div suppressHydrationWarning className="flex gap-4 items-center">
{(authKey.used && !authKey.reusable) || {(authKey.used && !authKey.reusable) ||
new Date(authKey.expiration) < new Date() ? undefined : ( new Date(authKey.expiration) < new Date() ? undefined : (
<ExpireKey authKey={authKey} /> <ExpireKey authKey={authKey} />

View File

@ -42,7 +42,10 @@ export default function UserRow({ user, role }: UserRowProps) {
<p>{mapRoleToName(role)}</p> <p>{mapRoleToName(role)}</p>
</td> </td>
<td className="pl-0.5 py-2"> <td className="pl-0.5 py-2">
<p className="text-sm text-headplane-600 dark:text-headplane-300"> <p
suppressHydrationWarning
className="text-sm text-headplane-600 dark:text-headplane-300"
>
{new Date(user.createdAt).toLocaleDateString()} {new Date(user.createdAt).toLocaleDateString()}
</p> </p>
</td> </td>
@ -54,7 +57,9 @@ export default function UserRow({ user, role }: UserRowProps) {
)} )}
> >
<StatusCircle isOnline={isOnline} className="w-4 h-4" /> <StatusCircle isOnline={isOnline} className="w-4 h-4" />
<p>{isOnline ? 'Connected' : new Date(lastSeen).toLocaleString()}</p> <p suppressHydrationWarning>
{isOnline ? 'Connected' : new Date(lastSeen).toLocaleString()}
</p>
</span> </span>
</td> </td>
<td className="py-2 pr-0.5"> <td className="py-2 pr-0.5">

View File

@ -12,7 +12,6 @@ import {
} from 'react-router'; } from 'react-router';
import Button from '~/components/Button'; import Button from '~/components/Button';
import Card from '~/components/Card'; import Card from '~/components/Card';
import Code from '~/components/Code';
import Link from '~/components/Link'; import Link from '~/components/Link';
import Options from '~/components/Options'; import Options from '~/components/Options';
import StatusCircle from '~/components/StatusCircle'; import StatusCircle from '~/components/StatusCircle';
@ -21,6 +20,7 @@ import { Machine } from '~/types';
import cn from '~/utils/cn'; import cn from '~/utils/cn';
import { useLiveData } from '~/utils/live-data'; import { useLiveData } from '~/utils/live-data';
import log from '~/utils/log'; import log from '~/utils/log';
import toast from '~/utils/toast';
export async function loader({ export async function loader({
request, request,
@ -152,20 +152,19 @@ export default function Page() {
} }
> >
<Button <Button
variant="heavy" className="flex text-md font-mono"
className={cn( onPress={async () => {
'my-4 px-0 w-full pointer-events-none', await navigator.clipboard.writeText(
'hover:bg-initial focus:ring-0', 'curl -fsSL https://tailscale.com/install.sh | sh',
)} );
>
<Code toast('Copied to clipboard');
isCopyable }}
className="bg-transparent pointer-events-auto mx-0"
> >
curl -fsSL https://tailscale.com/install.sh | sh curl -fsSL https://tailscale.com/install.sh | sh
</Code>
</Button> </Button>
<p className="text-end text-sm"> <p className="text-xs mt-1 text-headplane-600 dark:text-headplane-300 text-center">
Click this button to copy the command.{' '}
<Link <Link
name="Linux installation script" name="Linux installation script"
to="https://github.com/tailscale/tailscale/blob/main/scripts/installer.sh" to="https://github.com/tailscale/tailscale/blob/main/scripts/installer.sh"