From a53fd31d272cc97d48042dc2a708b95f9872b6a6 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Mon, 9 Mar 2026 13:56:03 -0400 Subject: [PATCH] chore: combine link usage --- app/components/Footer.tsx | 4 +- app/components/Link.tsx | 32 ------------ app/components/error-banner.tsx | 4 +- app/components/link.tsx | 52 +++++++++++++++++++ app/layouts/no-access.tsx | 4 +- app/routes/auth/login/config-error.tsx | 3 +- app/routes/auth/login/page.tsx | 9 ++-- app/routes/dns/components/manage-records.tsx | 8 ++- .../machines/components/machine-row.tsx | 2 +- app/routes/machines/dialogs/tags.tsx | 11 ++-- app/routes/machines/machine.tsx | 14 +++-- app/routes/machines/overview.tsx | 3 +- .../auth-keys/dialogs/add-auth-key.tsx | 3 +- app/routes/settings/auth-keys/overview.tsx | 8 +-- app/routes/settings/overview.tsx | 13 ++--- app/routes/settings/restrictions/overview.tsx | 9 ++-- app/routes/users/components/manage-banner.tsx | 4 +- app/routes/users/onboarding.tsx | 4 +- 18 files changed, 114 insertions(+), 73 deletions(-) delete mode 100644 app/components/Link.tsx create mode 100644 app/components/link.tsx diff --git a/app/components/Footer.tsx b/app/components/Footer.tsx index d377b6f..29a6417 100644 --- a/app/components/Footer.tsx +++ b/app/components/Footer.tsx @@ -1,6 +1,6 @@ import { CircleX } from "lucide-react"; -import Link from "~/components/Link"; +import Link from "~/components/link"; import cn from "~/utils/cn"; interface FooterProps { @@ -28,7 +28,7 @@ export default function Footer({ url, debug, healthy }: FooterProps) {

Headplane is free. Please consider{" "} - + donating {" "} to support development.{" "} diff --git a/app/components/Link.tsx b/app/components/Link.tsx deleted file mode 100644 index a090dd1..0000000 --- a/app/components/Link.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { ExternalLink } from "lucide-react"; - -import cn from "~/utils/cn"; - -export interface LinkProps { - to: string; - name: string; - children: string; - className?: string; -} - -export default function Link({ to, name: alt, children, className }: LinkProps) { - return ( - - {children} - - - ); -} diff --git a/app/components/error-banner.tsx b/app/components/error-banner.tsx index 5e15515..cf9fd84 100644 --- a/app/components/error-banner.tsx +++ b/app/components/error-banner.tsx @@ -6,7 +6,7 @@ import cn from "~/utils/cn"; import Card from "./Card"; import Code from "./Code"; -import Link from "./Link"; +import Link from "./link"; export function getErrorMessage(error: Error | unknown): { title: string; @@ -137,7 +137,7 @@ export function getErrorMessage(error: Error | unknown): { An unexpected error occurred which is most likely a bug. Please consider reporting filing an issue on the{" "} - + Headplane GitHub {" "} repository with the details below. diff --git a/app/components/link.tsx b/app/components/link.tsx new file mode 100644 index 0000000..2517c03 --- /dev/null +++ b/app/components/link.tsx @@ -0,0 +1,52 @@ +import { ExternalLink } from "lucide-react"; +import type { JSX } from "react"; +import { Link as RouterLink } from "react-router"; + +import cn from "~/utils/cn"; + +const linkStyles = cn( + "inline-flex items-center gap-x-0.5", + "text-blue-500 hover:text-blue-700", + "dark:text-blue-400 dark:hover:text-blue-300", + "focus:ring-offset-1 rounded-md", + "focus:outline-hidden focus:ring-2 focus:ring-indigo-500/40", + "dark:focus:ring-indigo-400/40 dark:focus:ring-offset-mist-900", +); + +export type LinkProps = + | { + isExternal: true; + to: string; + name: string; + children: string; + className?: string; + } + | { + isExternal?: false; + to: string; + children?: string; + className?: string; + }; + +export default function Link(props: LinkProps): JSX.Element { + if (props.isExternal) { + return ( + + {props.children} + + + ); + } + + return ( + + {props.children} + + ); +} diff --git a/app/layouts/no-access.tsx b/app/layouts/no-access.tsx index c0b147b..4bee4d0 100644 --- a/app/layouts/no-access.tsx +++ b/app/layouts/no-access.tsx @@ -3,7 +3,7 @@ import { Form } from "react-router"; import Button from "~/components/Button"; import Card from "~/components/Card"; -import Link from "~/components/Link"; +import Link from "~/components/link"; import Options from "~/components/Options"; import toast from "~/utils/toast"; @@ -58,6 +58,7 @@ export default function NoAccess({ linkedUserName, osValue }: NoAccessProps) {

Click this button to copy the command.{" "} @@ -112,6 +113,7 @@ export default function NoAccess({ linkedUserName, osValue }: NoAccessProps) {
You can also download Tailscale on the{" "} diff --git a/app/routes/auth/login/config-error.tsx b/app/routes/auth/login/config-error.tsx index 613947c..65213ac 100644 --- a/app/routes/auth/login/config-error.tsx +++ b/app/routes/auth/login/config-error.tsx @@ -2,7 +2,7 @@ import { AlertCircle, CloudOff } from "lucide-react"; import Card from "~/components/Card"; import Code from "~/components/Code"; -import Link from "~/components/Link"; +import Link from "~/components/link"; import { OidcConnectorError } from "~/server/web/oidc-connector"; export function OidcDiscoveryFailedNotice() { @@ -35,6 +35,7 @@ export function OidcConfigErrorNotice({ errors }: { errors: OidcConnectorError[] ))} {" "} diff --git a/app/routes/auth/login/page.tsx b/app/routes/auth/login/page.tsx index 3bc6727..3df13c4 100644 --- a/app/routes/auth/login/page.tsx +++ b/app/routes/auth/login/page.tsx @@ -1,12 +1,12 @@ import { AlertCircle } from "lucide-react"; import { useEffect, useState } from "react"; -import { Form, Link as RemixLink, redirect, useSearchParams } from "react-router"; +import { Form, redirect, useSearchParams } from "react-router"; import Button from "~/components/Button"; import Card from "~/components/Card"; import Code from "~/components/Code"; import Input from "~/components/Input"; -import Link from "~/components/Link"; +import Link from "~/components/link"; import { useLiveData } from "~/utils/live-data"; import type { Route } from "./+types/page"; @@ -103,6 +103,7 @@ export default function Page({ loaderData, actionData }: Route.ComponentProps) { Headplane is configured to use secure cookies, but this site is being served over an insecure connection and login will not work correctly.{" "} @@ -138,7 +139,7 @@ export default function Page({ loaderData, actionData }: Route.ComponentProps) { {isOidcConnectorEnabled ? ( - + - + ) : undefined}

diff --git a/app/routes/dns/components/manage-records.tsx b/app/routes/dns/components/manage-records.tsx index 1f60bd8..275d4b2 100644 --- a/app/routes/dns/components/manage-records.tsx +++ b/app/routes/dns/components/manage-records.tsx @@ -2,7 +2,7 @@ import { Form } from "react-router"; import Button from "~/components/Button"; import Code from "~/components/Code"; -import Link from "~/components/Link"; +import Link from "~/components/link"; import TableList from "~/components/TableList"; import cn from "~/utils/cn"; @@ -20,7 +20,11 @@ export default function ManageRecords({ records, isDisabled }: Props) {

Headscale supports adding custom DNS records to your Tailnet. As of now, only A{" "} and AAAA records are supported.{" "} - + Learn More

diff --git a/app/routes/machines/components/machine-row.tsx b/app/routes/machines/components/machine-row.tsx index c3c2530..d169288 100644 --- a/app/routes/machines/components/machine-row.tsx +++ b/app/routes/machines/components/machine-row.tsx @@ -1,8 +1,8 @@ import { ChevronDown, Copy } from "lucide-react"; import { useMemo } from "react"; -import { Link } from "react-router"; import Chip from "~/components/Chip"; +import Link from "~/components/link"; import Menu from "~/components/Menu"; import StatusCircle from "~/components/StatusCircle"; import { ExitNodeTag } from "~/components/tags/ExitNode"; diff --git a/app/routes/machines/dialogs/tags.tsx b/app/routes/machines/dialogs/tags.tsx index ce53f41..8f0b296 100644 --- a/app/routes/machines/dialogs/tags.tsx +++ b/app/routes/machines/dialogs/tags.tsx @@ -2,13 +2,12 @@ import { Plus, TagsIcon, X } from "lucide-react"; import { useEffect, useMemo, useRef, useState } from "react"; import { useFetcher } from "react-router"; -import type { Machine } from "~/types"; - import Button from "~/components/Button"; import Dialog from "~/components/Dialog"; -import Link from "~/components/Link"; +import Link from "~/components/link"; import Select from "~/components/Select"; import TableList from "~/components/TableList"; +import type { Machine } from "~/types"; import cn from "~/utils/cn"; interface TagsProps { @@ -73,7 +72,11 @@ export default function Tags({ machine, isOpen, setIsOpen, existingTags }: TagsP Edit ACL tags for {machine.givenName} ACL tags can be used to reference machines in your ACL policies. See the{" "} - + Tailscale documentation {" "} for more information. diff --git a/app/routes/machines/machine.tsx b/app/routes/machines/machine.tsx index a889337..bde4d0b 100644 --- a/app/routes/machines/machine.tsx +++ b/app/routes/machines/machine.tsx @@ -1,12 +1,12 @@ import { CheckCircle, CircleSlash, Info, UserCircle } from "lucide-react"; import { useMemo, useState } from "react"; -import { data, Link as RemixLink } from "react-router"; +import { data } from "react-router"; import Attribute from "~/components/Attribute"; import Button from "~/components/Button"; import Card from "~/components/Card"; import Chip from "~/components/Chip"; -import Link from "~/components/Link"; +import Link from "~/components/link"; import StatusCircle from "~/components/StatusCircle"; import Tooltip from "~/components/Tooltip"; import cn from "~/utils/cn"; @@ -75,9 +75,9 @@ export default function Page({ return (

- + All Machines - + / {node.givenName}

@@ -129,7 +129,11 @@ export default function Page({

Subnets let you expose physical network routes onto Tailscale.{" "} - + Learn More

diff --git a/app/routes/machines/overview.tsx b/app/routes/machines/overview.tsx index e5f8455..2f9741f 100644 --- a/app/routes/machines/overview.tsx +++ b/app/routes/machines/overview.tsx @@ -3,7 +3,7 @@ import { useMemo, useState } from "react"; import Code from "~/components/Code"; import Input from "~/components/Input"; -import Link from "~/components/Link"; +import Link from "~/components/link"; import Tooltip from "~/components/Tooltip"; import { Capabilities } from "~/server/web/roles"; import cn from "~/utils/cn"; @@ -151,6 +151,7 @@ export default function Page({ loaderData }: Route.ComponentProps) {

Manage the devices connected to your Tailnet.{" "} diff --git a/app/routes/settings/auth-keys/dialogs/add-auth-key.tsx b/app/routes/settings/auth-keys/dialogs/add-auth-key.tsx index c0701ae..3784b80 100644 --- a/app/routes/settings/auth-keys/dialogs/add-auth-key.tsx +++ b/app/routes/settings/auth-keys/dialogs/add-auth-key.tsx @@ -5,7 +5,7 @@ import Button from "~/components/Button"; import Code from "~/components/Code"; import Dialog from "~/components/Dialog"; import Input from "~/components/Input"; -import Link from "~/components/Link"; +import Link from "~/components/link"; import NumberInput from "~/components/NumberInput"; import Select from "~/components/Select"; import Switch from "~/components/Switch"; @@ -204,6 +204,7 @@ export default function AddAuthKey({ Devices authenticated with this key will be automatically removed once they go offline.{" "} diff --git a/app/routes/settings/auth-keys/overview.tsx b/app/routes/settings/auth-keys/overview.tsx index 4d82991..e6395f5 100644 --- a/app/routes/settings/auth-keys/overview.tsx +++ b/app/routes/settings/auth-keys/overview.tsx @@ -1,9 +1,8 @@ import { FileKey2 } from "lucide-react"; import { useMemo, useState } from "react"; -import { Link as RemixLink } from "react-router"; import Code from "~/components/Code"; -import Link from "~/components/Link"; +import Link from "~/components/link"; import Notice from "~/components/Notice"; import Select from "~/components/Select"; import TableList from "~/components/TableList"; @@ -167,9 +166,9 @@ export default function Page({ return (

- + Settings - + / Pre-Auth Keys

{!access ? ( @@ -194,6 +193,7 @@ export default function Page({ Headscale fully supports pre-authentication keys in order to easily add devices to your Tailnet. To learn more about using pre-authentication keys, visit the{" "} diff --git a/app/routes/settings/overview.tsx b/app/routes/settings/overview.tsx index f7dfe24..31e6dfc 100644 --- a/app/routes/settings/overview.tsx +++ b/app/routes/settings/overview.tsx @@ -1,7 +1,6 @@ import { ArrowRight } from "lucide-react"; -import { Link as RemixLink } from "react-router"; -import Link from "~/components/Link"; +import Link from "~/components/link"; import type { Route } from "./+types/overview"; @@ -30,6 +29,7 @@ export default function Page({ loaderData: { config, isOidcEnabled } }: Route.Co Headscale fully supports pre-authentication keys in order to easily add devices to your Tailnet. To learn more about using pre-authentication keys, visit the{" "} @@ -37,12 +37,12 @@ export default function Page({ loaderData: { config, isOidcEnabled } }: Route.Co

- +
Manage Auth Keys
-
+ {config && isOidcEnabled ? ( <>
@@ -53,6 +53,7 @@ export default function Page({ loaderData: { config, isOidcEnabled } }: Route.Co Tailnet to only certain users or groups and Headplane will also respect these settings when authenticating.{" "} @@ -60,12 +61,12 @@ export default function Page({ loaderData: { config, isOidcEnabled } }: Route.Co

- +
Manage Restrictions
-
+ ) : undefined}
diff --git a/app/routes/settings/restrictions/overview.tsx b/app/routes/settings/restrictions/overview.tsx index f177ad0..658b167 100644 --- a/app/routes/settings/restrictions/overview.tsx +++ b/app/routes/settings/restrictions/overview.tsx @@ -1,6 +1,6 @@ -import { data, Link as RemixLink } from "react-router"; +import { data } from "react-router"; -import Link from "~/components/Link"; +import Link from "~/components/link"; import Notice from "~/components/Notice"; import { Capabilities } from "~/server/web/roles"; @@ -46,9 +46,9 @@ export default function Page({ loaderData: { access, writable, settings } }: Rou

- + Settings - + / Authentication Restrictions

{!access ? ( @@ -70,6 +70,7 @@ export default function Page({ loaderData: { access, writable, settings } }: Rou certain users or groups and Headplane will also respect these settings when authenticating.{" "} diff --git a/app/routes/users/components/manage-banner.tsx b/app/routes/users/components/manage-banner.tsx index 932ed61..2af9404 100644 --- a/app/routes/users/components/manage-banner.tsx +++ b/app/routes/users/components/manage-banner.tsx @@ -1,7 +1,7 @@ import { Building2, House, Key } from "lucide-react"; import Card from "~/components/Card"; -import Link from "~/components/Link"; +import Link from "~/components/link"; import CreateUser from "../dialogs/create-user"; @@ -21,7 +21,7 @@ export default function ManageBanner({ oidc, isDisabled }: ManageBannerProps) { {oidc ? ( <> Users are managed through your{" "} - + OpenID Connect provider {". "} diff --git a/app/routes/users/onboarding.tsx b/app/routes/users/onboarding.tsx index e9f70bc..a59445d 100644 --- a/app/routes/users/onboarding.tsx +++ b/app/routes/users/onboarding.tsx @@ -5,7 +5,7 @@ import { Form, NavLink } from "react-router"; import Button from "~/components/Button"; import Card from "~/components/Card"; -import Link from "~/components/Link"; +import Link from "~/components/link"; import Options from "~/components/Options"; import StatusCircle from "~/components/StatusCircle"; import { findHeadscaleUserBySubject } from "~/server/web/headscale-identity"; @@ -216,6 +216,7 @@ export default function Page({

Click this button to copy the command.{" "} @@ -270,6 +271,7 @@ export default function Page({
You can also download Tailscale on the{" "}