chore: combine link usage

This commit is contained in:
Aarnav Tale
2026-03-09 13:56:03 -04:00
parent 37d0080cba
commit a53fd31d27
18 changed files with 114 additions and 73 deletions
+2 -2
View File
@@ -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) {
<div className={cn("text-xs leading-none", !healthy && "hidden md:block")}>
<p>
Headplane is free. Please consider{" "}
<Link to="https://github.com/sponsors/tale" name="Aarnav's GitHub Sponsors">
<Link isExternal to="https://github.com/sponsors/tale" name="Aarnav's GitHub Sponsors">
donating
</Link>{" "}
to support development.{" "}
-32
View File
@@ -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 (
<a
href={to}
aria-label={alt}
target="_blank"
rel="noreferrer"
className={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:outline-hidden focus:ring-2 focus:ring-indigo-500/40 focus:ring-offset-1 rounded-md",
"dark:focus:ring-indigo-400/40 dark:focus:ring-offset-mist-900",
className,
)}
>
{children}
<ExternalLink className="w-3.5" />
</a>
);
}
+2 -2
View File
@@ -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): {
<Card.Text>
An unexpected error occurred which is most likely a bug. Please consider reporting
filing an issue on the{" "}
<Link name="Headplane GitHub" to="https://github.com/tale/headplane/issues">
<Link isExternal name="Headplane GitHub" to="https://github.com/tale/headplane/issues">
Headplane GitHub
</Link>{" "}
repository with the details below.
+52
View File
@@ -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 (
<a
href={props.to}
aria-label={props.name}
target="_blank"
rel="noreferrer"
className={cn(linkStyles, props.className)}
>
{props.children}
<ExternalLink className="w-3.5" />
</a>
);
}
return (
<RouterLink to={props.to} className={cn(linkStyles, props.className)}>
{props.children}
</RouterLink>
);
}
+3 -1
View File
@@ -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) {
<p className="mt-1 text-center text-xs text-mist-600 dark:text-mist-300">
Click this button to copy the command.{" "}
<Link
isExternal
name="Linux installation script"
to="https://github.com/tailscale/tailscale/blob/main/scripts/installer.sh"
>
@@ -112,6 +113,7 @@ export default function NoAccess({ linkedUserName, osValue }: NoAccessProps) {
<br />
You can also download Tailscale on the{" "}
<Link
isExternal
name="macOS App Store"
to="https://apps.apple.com/ca/app/tailscale/id1475387142"
>
+2 -1
View File
@@ -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[]
))}
</ul>{" "}
<Link
isExternal
name="Headplane OIDC Issues"
to="https://headplane.net/configuration/sso#troubleshooting"
>
+5 -4
View File
@@ -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.{" "}
<Link
isExternal
name="Headplane Common Issues"
to="https://headplane.net/configuration/common-issues#issue-logging-in-does-not-do-anything"
>
@@ -138,7 +139,7 @@ export default function Page({ loaderData, actionData }: Route.ComponentProps) {
</Button>
</Form>
{isOidcConnectorEnabled ? (
<RemixLink to="/oidc/start">
<Link to="/oidc/start">
<Button
className="mt-2 w-full"
isDisabled={oidcErrorCodes.length > 0}
@@ -146,7 +147,7 @@ export default function Page({ loaderData, actionData }: Route.ComponentProps) {
>
Single Sign-On
</Button>
</RemixLink>
</Link>
) : undefined}
</Card>
</div>
+6 -2
View File
@@ -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) {
<p>
Headscale supports adding custom DNS records to your Tailnet. As of now, only <Code>A</Code>{" "}
and <Code>AAAA</Code> records are supported.{" "}
<Link name="Headscale DNS Records documentation" to="https://headscale.net/stable/ref/dns">
<Link
isExternal
name="Headscale DNS Records documentation"
to="https://headscale.net/stable/ref/dns"
>
Learn More
</Link>
</p>
@@ -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";
+7 -4
View File
@@ -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
<Dialog.Title>Edit ACL tags for {machine.givenName}</Dialog.Title>
<Dialog.Text>
ACL tags can be used to reference machines in your ACL policies. See the{" "}
<Link name="Tailscale documentation" to="https://tailscale.com/kb/1068/acl-tags">
<Link
isExternal
name="Tailscale documentation"
to="https://tailscale.com/kb/1068/acl-tags"
>
Tailscale documentation
</Link>{" "}
for more information.
+9 -5
View File
@@ -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 (
<div>
<p className="text-md mb-8">
<RemixLink className="font-medium" to="/machines">
<Link className="font-medium" to="/machines">
All Machines
</RemixLink>
</Link>
<span className="mx-2">/</span>
{node.givenName}
</p>
@@ -129,7 +129,11 @@ export default function Page({
<div className="mb-4 flex items-center justify-between">
<p>
Subnets let you expose physical network routes onto Tailscale.{" "}
<Link name="Tailscale Subnets Documentation" to="https://tailscale.com/kb/1019/subnets">
<Link
isExternal
name="Tailscale Subnets Documentation"
to="https://tailscale.com/kb/1019/subnets"
>
Learn More
</Link>
</p>
+2 -1
View File
@@ -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) {
<p>
Manage the devices connected to your Tailnet.{" "}
<Link
isExternal
name="Tailscale Manage Devices Documentation"
to="https://tailscale.com/kb/1372/manage-devices"
>
@@ -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.{" "}
<Link
isExternal
name="Tailscale Ephemeral Nodes Documentation"
to="https://tailscale.com/kb/1111/ephemeral-nodes"
>
+4 -4
View File
@@ -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 (
<div className="flex flex-col md:w-2/3">
<p className="text-md mb-8">
<RemixLink className="font-medium" to="/settings">
<Link className="font-medium" to="/settings">
Settings
</RemixLink>
</Link>
<span className="mx-2">/</span> Pre-Auth Keys
</p>
{!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{" "}
<Link
isExternal
name="Tailscale Auth Keys documentation"
to="https://tailscale.com/kb/1085/auth-keys/"
>
+7 -6
View File
@@ -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{" "}
<Link
isExternal
name="Tailscale Auth Keys documentation"
to="https://tailscale.com/kb/1085/auth-keys/"
>
@@ -37,12 +37,12 @@ export default function Page({ loaderData: { config, isOidcEnabled } }: Route.Co
</Link>
</p>
</div>
<RemixLink to="/settings/auth-keys">
<Link to="/settings/auth-keys">
<div className="flex items-center text-lg font-medium">
Manage Auth Keys
<ArrowRight className="ml-2 h-5 w-5" />
</div>
</RemixLink>
</Link>
{config && isOidcEnabled ? (
<>
<div className="flex w-full flex-col sm:w-2/3">
@@ -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.{" "}
<Link
isExternal
name="Headscale OIDC documentation"
to="https://headscale.net/stable/ref/oidc/#basic-configuration"
>
@@ -60,12 +61,12 @@ export default function Page({ loaderData: { config, isOidcEnabled } }: Route.Co
</Link>
</p>
</div>
<RemixLink to="/settings/restrictions">
<Link to="/settings/restrictions">
<div className="flex items-center text-lg font-medium">
Manage Restrictions
<ArrowRight className="ml-2 h-5 w-5" />
</div>
</RemixLink>
</Link>
</>
) : undefined}
</div>
@@ -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
<div className="flex max-w-(--breakpoint-lg) flex-col gap-4">
<div className="flex w-full flex-col sm:w-2/3">
<p className="text-md mb-4">
<RemixLink className="font-medium" to="/settings">
<Link className="font-medium" to="/settings">
Settings
</RemixLink>
</Link>
<span className="mx-2">/</span> Authentication Restrictions
</p>
{!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.{" "}
<Link
isExternal
name="Headscale OIDC documentation"
to="https://headscale.net/stable/ref/oidc/#basic-configuration"
>
@@ -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{" "}
<Link name="OIDC Provider" to={oidc.issuer}>
<Link isExternal name="OIDC Provider" to={oidc.issuer}>
OpenID Connect provider
</Link>
{". "}
+3 -1
View File
@@ -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({
<p className="mt-1 text-center text-xs text-mist-600 dark:text-mist-300">
Click this button to copy the command.{" "}
<Link
isExternal
name="Linux installation script"
to="https://github.com/tailscale/tailscale/blob/main/scripts/installer.sh"
>
@@ -270,6 +271,7 @@ export default function Page({
<br />
You can also download Tailscale on the{" "}
<Link
isExternal
name="macOS App Store"
to="https://apps.apple.com/ca/app/tailscale/id1475387142"
>