fix: resolve type errors and lint components/routes

This commit is contained in:
Aarnav Tale 2025-01-06 08:18:46 +05:30
parent 414b95c293
commit 6745ee8529
No known key found for this signature in database
13 changed files with 63 additions and 28 deletions

View File

@ -1,4 +1,4 @@
import { useState, type HTMLProps } from 'react'; import { useState, HTMLProps } from 'react';
import { CopyIcon, CheckIcon } from '@primer/octicons-react'; import { CopyIcon, CheckIcon } from '@primer/octicons-react';
import { cn } from '~/utils/cn'; import { cn } from '~/utils/cn';
import { toast } from '~/components/Toaster'; import { toast } from '~/components/Toaster';
@ -20,7 +20,7 @@ export default function Code(props: Props) {
> >
{props.children} {props.children}
</code> </code>
{props.isCopyable && props.children ? ( {props.isCopyable ? (
<button <button
className={cn( className={cn(
'ml-1 p-1 rounded-md', 'ml-1 p-1 rounded-md',
@ -29,6 +29,10 @@ export default function Code(props: Props) {
'inline-flex items-center justify-center', 'inline-flex items-center justify-center',
)} )}
onClick={() => { onClick={() => {
if (!props.children) {
throw new Error('Made copyable without children');
}
navigator.clipboard.writeText(props.children.join('')); navigator.clipboard.writeText(props.children.join(''));
toast('Copied to clipboard'); toast('Copied to clipboard');
setIsCopied(true); setIsCopied(true);

View File

@ -1,12 +1,16 @@
import { cn } from '~/utils/cn'; import { cn } from '~/utils/cn';
import Link from '~/components/Link'; import Link from '~/components/Link';
declare global {
const __VERSION__: string;
}
interface FooterProps { interface FooterProps {
url: string; url: string;
debug: boolean; debug: boolean;
} }
export default function Footer({ url, debug, integration }: FooterProps) { export default function Footer({ url, debug }: FooterProps) {
return ( return (
<footer <footer
className={cn( className={cn(

View File

@ -1,7 +1,15 @@
import { XIcon } from '@primer/octicons-react'; import { XIcon } from '@primer/octicons-react';
import { type AriaToastProps, useToast, useToastRegion } from '@react-aria/toast'; import {
import { ToastQueue, type ToastState, useToastQueue } from '@react-stately/toast'; AriaToastProps,
import { type ReactNode, useRef } from 'react'; useToast,
useToastRegion,
} from '@react-aria/toast';
import {
ToastQueue,
ToastState,
useToastQueue,
} from '@react-stately/toast';
import { ReactNode, useRef } from 'react';
import { Button } from 'react-aria-components'; import { Button } from 'react-aria-components';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { ClientOnly } from 'remix-utils/client-only'; import { ClientOnly } from 'remix-utils/client-only';
@ -14,7 +22,6 @@ type ToastProps = AriaToastProps<ReactNode> & {
function Toast({ state, ...properties }: ToastProps) { function Toast({ state, ...properties }: ToastProps) {
const reference = useRef(null); const reference = useRef(null);
// @ts-expect-error: RefObject doesn't map to FocusableElement?
const { toastProps, titleProps, closeButtonProps } = useToast( const { toastProps, titleProps, closeButtonProps } = useToast(
properties, properties,
state, state,
@ -58,13 +65,11 @@ export function Toaster() {
const reference = useRef(null); const reference = useRef(null);
const state = useToastQueue(toasts); const state = useToastQueue(toasts);
// @ts-expect-error: React 19 has weird types for Portal vs Node
const { regionProps } = useToastRegion({}, state, reference); const { regionProps } = useToastRegion({}, state, reference);
return ( return (
<ClientOnly> <ClientOnly>
{ {
// @ts-expect-error: Portal doesn't match Node in React 19 yet
() => () =>
createPortal( createPortal(
state.visibleToasts.length >= 0 ? ( state.visibleToasts.length >= 0 ? (

View File

@ -5,8 +5,8 @@ import {
PencilIcon, PencilIcon,
} from '@primer/octicons-react'; } from '@primer/octicons-react';
import type { ActionFunctionArgs, LoaderFunctionArgs } from 'react-router'; import type { ActionFunctionArgs, LoaderFunctionArgs } from 'react-router';
import { useLoaderData, useRevalidator } from 'react-router'; import { useLoaderData, useRevalidator, useFetcher } from 'react-router';
import { useDebounceFetcher } from 'remix-utils/use-debounce-fetcher'; //import { useDebounceFetcher } from 'remix-utils/use-debounce-fetcher';
import { useEffect, useState, useMemo } from 'react'; import { useEffect, useState, useMemo } from 'react';
import { Tab, TabList, TabPanel, Tabs } from 'react-aria-components'; import { Tab, TabList, TabPanel, Tabs } from 'react-aria-components';
import { setTimeout } from 'node:timers/promises'; import { setTimeout } from 'node:timers/promises';
@ -149,7 +149,7 @@ export async function action({ request }: ActionFunctionArgs) {
export default function Page() { export default function Page() {
const data = useLoaderData<typeof loader>(); const data = useLoaderData<typeof loader>();
const fetcher = useDebounceFetcher<typeof action>(); const fetcher = useFetcher<typeof action>();
const revalidator = useRevalidator(); const revalidator = useRevalidator();
const [acl, setAcl] = useState(data.policy ?? ''); const [acl, setAcl] = useState(data.policy ?? '');

View File

@ -1,4 +1,8 @@
import { type ActionFunctionArgs, type LoaderFunctionArgs, redirect } from 'react-router'; import {
type ActionFunctionArgs,
type LoaderFunctionArgs,
redirect,
} from 'react-router';
import { Form, useActionData, useLoaderData } from 'react-router'; import { Form, useActionData, useLoaderData } from 'react-router';
import { useMemo } from 'react'; import { useMemo } from 'react';

View File

@ -48,7 +48,7 @@ export default function Nameservers({ nameservers, isDisabled }: Props) {
interface ListProps { interface ListProps {
isGlobal: boolean; isGlobal: boolean;
isDisabled: boolean; isDisabled: boolean;
nameservers: string[]; nameservers: Record<string, string[]>;
name: string; name: string;
} }

View File

@ -43,22 +43,22 @@ export async function loader() {
export async function action({ request }: ActionFunctionArgs) { export async function action({ request }: ActionFunctionArgs) {
const session = await getSession(request.headers.get('Cookie')); const session = await getSession(request.headers.get('Cookie'));
if (!session.has('hsApiKey')) { if (!session.has('hsApiKey')) {
return send({ success: false }, 401); return data({ success: false }, { status: 401 });
} }
const context = await loadContext(); const context = await loadContext();
if (!context.config.write) { if (!context.config.write) {
return send({ success: false }, 403); return data({ success: false }, { status: 403 });
} }
const data = (await request.json()) as Record<string, unknown>; const patch = (await request.json()) as Record<string, unknown>;
await patchConfig(data); await patchConfig(patch);
if (context.integration?.onConfigChange) { if (context.integration?.onConfigChange) {
await context.integration.onConfigChange(context.integration.context); await context.integration.onConfigChange(context.integration.context);
} }
return { success: true }; return data({ success: true });
} }
export default function Page() { export default function Page() {

View File

@ -1,6 +1,6 @@
import { ChevronDownIcon, CopyIcon } from '@primer/octicons-react'; import { ChevronDownIcon, CopyIcon } from '@primer/octicons-react';
import { Link } from 'react-router'; import { Link } from 'react-router';
import { useMemo } from 'react';
import Menu from '~/components/Menu'; import Menu from '~/components/Menu';
import StatusCircle from '~/components/StatusCircle'; import StatusCircle from '~/components/StatusCircle';
import { toast } from '~/components/Toaster'; import { toast } from '~/components/Toaster';
@ -35,7 +35,11 @@ export default function MachineRow({ machine, routes, magic, users }: Props) {
: magic; : magic;
// This is much easier with Object.groupBy but it's too new for us // This is much easier with Object.groupBy but it's too new for us
const { exit, subnet, subnetApproved } = routes.reduce( const { exit, subnet, subnetApproved } = routes.reduce<{
exit: Route[];
subnetApproved: Route[];
subnet: Route[];
}>(
(acc, route) => { (acc, route) => {
if (route.prefix === '::/0' || route.prefix === '0.0.0.0/0') { if (route.prefix === '::/0' || route.prefix === '0.0.0.0/0') {
acc.exit.push(route); acc.exit.push(route);

View File

@ -1,5 +1,5 @@
import { useFetcher } from 'react-router'; import { useFetcher } from 'react-router';
import { type Dispatch, type SetStateAction, useMemo } from 'react'; import { Dispatch, SetStateAction, useMemo } from 'react';
import Dialog from '~/components/Dialog'; import Dialog from '~/components/Dialog';
import Switch from '~/components/Switch'; import Switch from '~/components/Switch';
@ -18,7 +18,10 @@ export default function Routes({ machine, routes, state }: RoutesProps) {
const fetcher = useFetcher(); const fetcher = useFetcher();
// This is much easier with Object.groupBy but it's too new for us // This is much easier with Object.groupBy but it's too new for us
const { exit, subnet } = routes.reduce( const { exit, subnet } = routes.reduce<{
exit: Route[];
subnet: Route[];
}>(
(acc, route) => { (acc, route) => {
if (route.prefix === '::/0' || route.prefix === '0.0.0.0/0') { if (route.prefix === '::/0' || route.prefix === '0.0.0.0/0') {
acc.exit.push(route); acc.exit.push(route);

View File

@ -81,7 +81,11 @@ export default function Page() {
} }
// This is much easier with Object.groupBy but it's too new for us // This is much easier with Object.groupBy but it's too new for us
const { exit, subnet, subnetApproved } = routes.reduce( const { exit, subnet, subnetApproved } = routes.reduce<{
exit: Route[];
subnet: Route[];
subnetApproved: Route[];
}>(
(acc, route) => { (acc, route) => {
if (route.prefix === '::/0' || route.prefix === '0.0.0.0/0') { if (route.prefix === '::/0' || route.prefix === '0.0.0.0/0') {
acc.exit.push(route); acc.exit.push(route);
@ -133,7 +137,6 @@ export default function Page() {
</span> </span>
<MenuOptions <MenuOptions
className={cn('bg-ui-100 dark:bg-ui-800')}
machine={machine} machine={machine}
routes={routes} routes={routes}
users={users} users={users}

View File

@ -1,4 +1,9 @@
import { type DataRef, DndContext, useDraggable, useDroppable } from '@dnd-kit/core'; import {
DataRef,
DndContext,
useDraggable,
useDroppable,
} from '@dnd-kit/core';
import { PersonIcon } from '@primer/octicons-react'; import { PersonIcon } from '@primer/octicons-react';
import type { ActionFunctionArgs, LoaderFunctionArgs } from 'react-router'; import type { ActionFunctionArgs, LoaderFunctionArgs } from 'react-router';
import { useActionData, useLoaderData, useSubmit } from 'react-router'; import { useActionData, useLoaderData, useSubmit } from 'react-router';

View File

@ -22,5 +22,5 @@ export async function loader() {
} }
} }
return { status: 'OK' }; return data({ status: 'OK' });
} }

View File

@ -21,7 +21,10 @@
"linter": { "linter": {
"enabled": true, "enabled": true,
"rules": { "rules": {
"recommended": true "recommended": true,
"style": {
"useImportType": "off"
}
} }
}, },
"javascript": { "javascript": {