From 1dace9c9f7094ca7c24012c05df8be94c3a1ff25 Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 24 Sep 2026 09:47:26 -0400 Subject: [PATCH] Fix type in SelectedSite type and filter newts only on ssh creation --- .../resources/private/[niceId]/ssh/page.tsx | 4 ++-- .../resources/private/create/page.tsx | 8 +++---- .../settings/resources/public/create/page.tsx | 5 ++-- src/components/BrowserGatewayTargetForm.tsx | 24 ++++++++----------- src/components/HealthCheckCredenza.tsx | 12 ++++------ src/components/HealthChecksTable.tsx | 8 +++---- src/components/PrivateResourceSitesField.tsx | 6 ++--- src/components/PrivateResourceSshFields.tsx | 8 +++---- src/components/PrivateResourcesTable.tsx | 2 +- src/components/PublicResourcesTable.tsx | 4 ++-- src/components/SitesColumnFilterButton.tsx | 4 ++-- src/components/multi-site-selector.tsx | 10 ++++---- .../LauncherFilterPopover.tsx | 8 +++---- .../resource-launcher/ResourceLauncher.tsx | 4 ++-- src/components/site-selector.tsx | 12 +++++----- src/lib/privateResourceUtils.ts | 4 ++-- 16 files changed, 59 insertions(+), 64 deletions(-) diff --git a/src/app/[orgId]/settings/resources/private/[niceId]/ssh/page.tsx b/src/app/[orgId]/settings/resources/private/[niceId]/ssh/page.tsx index ba60e41e6..fbdecfa5c 100644 --- a/src/app/[orgId]/settings/resources/private/[niceId]/ssh/page.tsx +++ b/src/app/[orgId]/settings/resources/private/[niceId]/ssh/page.tsx @@ -24,7 +24,7 @@ import { useActionState, useMemo, useState } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; import { PrivateResourceSshFields } from "@app/components/PrivateResourceSshFields"; -import type { Selectedsite } from "@app/components/site-selector"; +import type { SelectedSite } from "@app/components/site-selector"; import { useSaveSiteResource } from "@app/hooks/useSaveSiteResource"; import { asAnyControl, @@ -84,7 +84,7 @@ export default function PrivateResourceSshPage() { setSelectedSites(first); form.setValue( "siteIds", - first.map((s: Selectedsite) => s.siteId), + first.map((s: SelectedSite) => s.siteId), { shouldValidate: true } ); } diff --git a/src/app/[orgId]/settings/resources/private/create/page.tsx b/src/app/[orgId]/settings/resources/private/create/page.tsx index b895ff5a3..22db4a8cd 100644 --- a/src/app/[orgId]/settings/resources/private/create/page.tsx +++ b/src/app/[orgId]/settings/resources/private/create/page.tsx @@ -27,7 +27,7 @@ import { FormMessage } from "@app/components/ui/form"; import { Input } from "@app/components/ui/input"; -import type { Selectedsite } from "@app/components/site-selector"; +import type { SelectedSite } from "@app/components/site-selector"; import { useEnvContext } from "@app/hooks/useEnvContext"; import { toast } from "@app/hooks/useToast"; import { createApiClient, formatAxiosError } from "@app/lib/api"; @@ -83,7 +83,7 @@ export default function CreatePrivateResourcePage() { ? Number(siteIdParam) : null; - const [selectedSites, setSelectedSites] = useState([]); + const [selectedSites, setSelectedSites] = useState([]); const [selectedProviders, setSelectedProviders] = useState< SelectedAiProvider[] >([]); @@ -124,10 +124,10 @@ export default function CreatePrivateResourcePage() { .then((res) => { const site = res.data.data; if (!site || site.orgId !== orgId) return; - const selected: Selectedsite = { + const selected: SelectedSite = { siteId: site.siteId, name: site.name, - type: site.type as Selectedsite["type"] + type: site.type as SelectedSite["type"] }; setSelectedSites([selected]); form.setValue("siteIds", [site.siteId]); diff --git a/src/app/[orgId]/settings/resources/public/create/page.tsx b/src/app/[orgId]/settings/resources/public/create/page.tsx index f5b5b2868..f05052001 100644 --- a/src/app/[orgId]/settings/resources/public/create/page.tsx +++ b/src/app/[orgId]/settings/resources/public/create/page.tsx @@ -28,7 +28,7 @@ import { import { BrowserGatewayTargetForm } from "@app/components/BrowserGatewayTargetForm"; import { SitesSelector, - type Selectedsite + type SelectedSite } from "@app/components/site-selector"; import { Button } from "@app/components/ui/button"; import { @@ -253,7 +253,7 @@ export default function Page() { "site" | "remote" >("site"); const [nativeSelectedSite, setNativeSelectedSite] = - useState(null); + useState(null); const [nativeSiteOpen, setNativeSiteOpen] = useState(false); useEffect(() => { @@ -1226,6 +1226,7 @@ export default function Page() { = BaseProps & { }; export type BrowserGatewayTargetFormProps = - | MultiSiteFormProps - | SingleSiteFormProps; + MultiSiteFormProps | SingleSiteFormProps; export function BrowserGatewayTargetForm( props: BrowserGatewayTargetFormProps @@ -90,7 +89,7 @@ export function BrowserGatewayTargetForm( const showMultiSiteDisclaimer = props.multiSite === true && - ((watchedSites as Selectedsite[] | undefined)?.length ?? 0) > 1; + ((watchedSites as SelectedSite[] | undefined)?.length ?? 0) > 1; return (
@@ -112,7 +111,7 @@ export function BrowserGatewayTargetForm( "aria-invalid:border-destructive aria-invalid:ring-destructive/20", props.multiSite === true ? ( - field.value as Selectedsite[] + field.value as SelectedSite[] )?.length === 0 && "text-muted-foreground" : !field.value && @@ -122,12 +121,12 @@ export function BrowserGatewayTargetForm( {props.multiSite === true ? formatMultiSitesSelectorLabel( - (field.value as Selectedsite[]) ?? + (field.value as SelectedSite[]) ?? [], t ) : (( - field.value as Selectedsite | null + field.value as SelectedSite | null )?.name ?? t("siteSelect"))} @@ -140,7 +139,7 @@ export function BrowserGatewayTargetForm( ( { field.onChange(site); @@ -179,8 +178,7 @@ export function BrowserGatewayTargetForm( onChange={field.onChange} value={ (watchedDestination as - | string - | undefined) ?? "" + string | undefined) ?? "" } /> @@ -205,9 +203,7 @@ export function BrowserGatewayTargetForm( onChange={field.onChange} value={ (watchedDestinationPort as - | string - | number - | undefined) ?? "" + string | number | undefined) ?? "" } /> diff --git a/src/components/HealthCheckCredenza.tsx b/src/components/HealthCheckCredenza.tsx index a1c81a72f..aed14e58f 100644 --- a/src/components/HealthCheckCredenza.tsx +++ b/src/components/HealthCheckCredenza.tsx @@ -46,7 +46,7 @@ import { PopoverTrigger } from "@/components/ui/popover"; import { SitesSelector } from "@app/components/site-selector"; -import type { Selectedsite } from "@app/components/site-selector"; +import type { SelectedSite } from "@app/components/site-selector"; import { CaretSortIcon } from "@radix-ui/react-icons"; import { cn } from "@app/lib/cn"; import { SwitchInput } from "@app/components/SwitchInput"; @@ -144,7 +144,7 @@ export function HealthCheckCredenza(props: HealthCheckCredenzaProps) { const t = useTranslations(); const api = createApiClient(useEnvContext()); const [loading, setLoading] = useState(false); - const [selectedSite, setSelectedSite] = useState(null); + const [selectedSite, setSelectedSite] = useState(null); const healthCheckSchema = z .object({ @@ -183,11 +183,9 @@ export function HealthCheckCredenza(props: HealthCheckCredenzaProps) { { message: t("healthCheckPortInvalid") } ), hcFollowRedirects: z.boolean(), - hcHostname: z - .string() - .refine((val) => !/\s/.test(val), { - message: t("healthCheckHostnameInvalid") - }), + hcHostname: z.string().refine((val) => !/\s/.test(val), { + message: t("healthCheckHostnameInvalid") + }), hcMode: z.string(), hcUnhealthyInterval: z.int().positive().min(5), hcTlsServerName: z.string(), diff --git a/src/components/HealthChecksTable.tsx b/src/components/HealthChecksTable.tsx index b4f273e5a..1c73076a1 100644 --- a/src/components/HealthChecksTable.tsx +++ b/src/components/HealthChecksTable.tsx @@ -28,7 +28,7 @@ import { Switch } from "@app/components/ui/switch"; import { toast } from "@app/hooks/useToast"; import { useEnvContext } from "@app/hooks/useEnvContext"; import { createApiClient, formatAxiosError } from "@app/lib/api"; -import { Selectedsite, SitesSelector } from "@app/components/site-selector"; +import { SelectedSite, SitesSelector } from "@app/components/site-selector"; import { ResourceSelector, SelectedResource @@ -59,7 +59,7 @@ type StandaloneHealthChecksTableProps = { healthChecks: HealthCheckRow[]; rowCount: number; pagination: PaginationState; - initialFilterSite?: Selectedsite | null; + initialFilterSite?: SelectedSite | null; initialFilterResource?: SelectedResource | null; }; @@ -117,7 +117,7 @@ export default function HealthChecksTable({ const siteIdQ = searchParams.get("siteId"); const siteIdNum = siteIdQ ? parseInt(siteIdQ, 10) : NaN; - const selectedSite: Selectedsite | null = useMemo(() => { + const selectedSite: SelectedSite | null = useMemo(() => { if (!siteIdQ || !Number.isInteger(siteIdNum) || siteIdNum <= 0) { return null; } @@ -227,7 +227,7 @@ export default function HealthChecksTable({ setResourceFilterOpen(false); }; - const onPickSite = (site: Selectedsite) => { + const onPickSite = (site: SelectedSite) => { handleFilterChange("siteId", String(site.siteId)); setSiteFilterOpen(false); }; diff --git a/src/components/PrivateResourceSitesField.tsx b/src/components/PrivateResourceSitesField.tsx index 0b0b930cb..5b1e0af31 100644 --- a/src/components/PrivateResourceSitesField.tsx +++ b/src/components/PrivateResourceSitesField.tsx @@ -5,7 +5,7 @@ import { formatMultiSitesSelectorLabel } from "@app/components/multi-site-selector"; import { SitesSelector } from "@app/components/site-selector"; -import type { Selectedsite } from "@app/components/site-selector"; +import type { SelectedSite } from "@app/components/site-selector"; import { Button } from "@app/components/ui/button"; import { FormControl, @@ -28,8 +28,8 @@ import { PrivateResourceMultiSiteRoutingHelp } from "@app/components/PrivateReso type PrivateResourceSitesFieldProps = { control: Control; orgId: string; - selectedSites: Selectedsite[]; - onSelectedSitesChange: (sites: Selectedsite[]) => void; + selectedSites: SelectedSite[]; + onSelectedSitesChange: (sites: SelectedSite[]) => void; siteIdsFieldName?: FieldPath; singleSite?: boolean; }; diff --git a/src/components/PrivateResourceSshFields.tsx b/src/components/PrivateResourceSshFields.tsx index 04ac155de..a727c9649 100644 --- a/src/components/PrivateResourceSshFields.tsx +++ b/src/components/PrivateResourceSshFields.tsx @@ -25,7 +25,7 @@ import { tierMatrix } from "@server/lib/billing/tierMatrix"; import { useTranslations } from "next-intl"; import { useState, type ReactNode } from "react"; import type { Control, UseFormSetValue, UseFormWatch } from "react-hook-form"; -import type { Selectedsite } from "@app/components/site-selector"; +import type { SelectedSite } from "@app/components/site-selector"; type PrivateResourceSshFieldsProps = { control: Control; @@ -33,8 +33,8 @@ type PrivateResourceSshFieldsProps = { watch: UseFormWatch; orgId?: string; disabled?: boolean; - selectedSites: Selectedsite[]; - onSelectedSitesChange: (sites: Selectedsite[]) => void; + selectedSites: SelectedSite[]; + onSelectedSitesChange: (sites: SelectedSite[]) => void; labelPrefix?: "create" | "edit"; showSshSettings?: boolean; layout?: "default" | "wizard"; @@ -101,7 +101,7 @@ export function PrivateResourceSshFields({ onSelectedSitesChange(first); setValue( "siteIds", - first.map((s: Selectedsite) => s.siteId), + first.map((s: SelectedSite) => s.siteId), { shouldValidate: true } ); } diff --git a/src/components/PrivateResourcesTable.tsx b/src/components/PrivateResourcesTable.tsx index d15d53205..02b4500ac 100644 --- a/src/components/PrivateResourcesTable.tsx +++ b/src/components/PrivateResourcesTable.tsx @@ -7,7 +7,7 @@ import { ResourceSitesStatusCell, type ResourceSiteRow } from "@app/components/ResourceSitesStatusCell"; -import { Selectedsite, SitesSelector } from "@app/components/site-selector"; +import { SelectedSite, SitesSelector } from "@app/components/site-selector"; import { Badge } from "@app/components/ui/badge"; import { Button } from "@app/components/ui/button"; import { ExtendedColumnDef } from "@app/components/ui/data-table"; diff --git a/src/components/PublicResourcesTable.tsx b/src/components/PublicResourcesTable.tsx index 4a00b4555..22f25c106 100644 --- a/src/components/PublicResourcesTable.tsx +++ b/src/components/PublicResourcesTable.tsx @@ -7,7 +7,7 @@ import { ResourceSitesStatusCell, type ResourceSiteRow } from "@app/components/ResourceSitesStatusCell"; -import { Selectedsite } from "@app/components/site-selector"; +import { SelectedSite } from "@app/components/site-selector"; import { Button } from "@app/components/ui/button"; import { ExtendedColumnDef } from "@app/components/ui/data-table"; import { @@ -105,7 +105,7 @@ type ProxyResourcesTableProps = { orgId: string; pagination: PaginationState; rowCount: number; - initialFilterSite?: Selectedsite | null; + initialFilterSite?: SelectedSite | null; /** Certificates prefetched on the server, keyed by full domain. */ initialCertificates?: GetBatchedCertificateResponse; }; diff --git a/src/components/SitesColumnFilterButton.tsx b/src/components/SitesColumnFilterButton.tsx index 2e138f564..ae363f0e5 100644 --- a/src/components/SitesColumnFilterButton.tsx +++ b/src/components/SitesColumnFilterButton.tsx @@ -3,7 +3,7 @@ import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover"; import { cn } from "@app/lib/cn"; import { dataTableFilterPopoverContentClassName } from "@app/lib/dataTableFilterPopover"; import { CheckIcon, Funnel } from "lucide-react"; -import { SiteOnlineStatus, type Selectedsite } from "./site-selector"; +import { SiteOnlineStatus, type SelectedSite } from "./site-selector"; import { Button } from "./ui/button"; import { useTranslations } from "next-intl"; import { Badge } from "./ui/badge"; @@ -62,7 +62,7 @@ export function SitesColumnFilterButton({ // always include the selected site in the list of sites shown const sitesShown = useMemo(() => { - const allSites: Array = [...sites]; + const allSites: Array = [...sites]; if ( debouncedQuery.trim().length === 0 && selectedSite && diff --git a/src/components/multi-site-selector.tsx b/src/components/multi-site-selector.tsx index ef2312988..a4e7c0bdd 100644 --- a/src/components/multi-site-selector.tsx +++ b/src/components/multi-site-selector.tsx @@ -12,12 +12,12 @@ import { import { Checkbox } from "./ui/checkbox"; import { useTranslations } from "next-intl"; import { useDebounce } from "use-debounce"; -import { SiteOnlineStatus, type Selectedsite } from "./site-selector"; +import { SiteOnlineStatus, type SelectedSite } from "./site-selector"; export type MultiSitesSelectorProps = { orgId: string; - selectedSites: Selectedsite[]; - onSelectionChange: (sites: Selectedsite[]) => void; + selectedSites: SelectedSite[]; + onSelectionChange: (sites: SelectedSite[]) => void; filterTypes?: string[]; scope?: "org" | "launcher"; onClear?: () => void; @@ -25,7 +25,7 @@ export type MultiSitesSelectorProps = { }; export function formatMultiSitesSelectorLabel( - selectedSites: Selectedsite[], + selectedSites: SelectedSite[], t: (key: string, values?: { count: number }) => string ): string { if (selectedSites.length === 0) { @@ -91,7 +91,7 @@ export function MultiSitesSelector({ [selectedSites] ); - const toggleSite = (site: Selectedsite) => { + const toggleSite = (site: SelectedSite) => { if (selectedIds.has(site.siteId)) { onSelectionChange( selectedSites.filter((s) => s.siteId !== site.siteId) diff --git a/src/components/resource-launcher/LauncherFilterPopover.tsx b/src/components/resource-launcher/LauncherFilterPopover.tsx index 90e20f2d7..061bfd0a6 100644 --- a/src/components/resource-launcher/LauncherFilterPopover.tsx +++ b/src/components/resource-launcher/LauncherFilterPopover.tsx @@ -23,13 +23,13 @@ import { useQuery } from "@tanstack/react-query"; import { useTranslations } from "next-intl"; import { ChevronsUpDown, Funnel } from "lucide-react"; import { useMemo, useState } from "react"; -import type { Selectedsite } from "@app/components/site-selector"; +import type { SelectedSite } from "@app/components/site-selector"; type LauncherFilterPopoverProps = { orgId: string; - selectedSites: Selectedsite[]; + selectedSites: SelectedSite[]; selectedLabels: SelectedLabel[]; - onSitesChange: (sites: Selectedsite[]) => void; + onSitesChange: (sites: SelectedSite[]) => void; onLabelsChange: (labels: SelectedLabel[]) => void; }; @@ -58,7 +58,7 @@ export function LauncherFilterPopover({ }) ); - const resolvedSelectedSites: Selectedsite[] = useMemo( + const resolvedSelectedSites: SelectedSite[] = useMemo( () => selectedSites.map((selected) => { const found = sites.find( diff --git a/src/components/resource-launcher/ResourceLauncher.tsx b/src/components/resource-launcher/ResourceLauncher.tsx index 33afedca8..f5d62c7b2 100644 --- a/src/components/resource-launcher/ResourceLauncher.tsx +++ b/src/components/resource-launcher/ResourceLauncher.tsx @@ -63,7 +63,7 @@ import { useTransition } from "react"; import { useDebouncedCallback } from "use-debounce"; -import type { Selectedsite } from "@app/components/site-selector"; +import type { SelectedSite } from "@app/components/site-selector"; import type { SelectedLabel } from "@app/components/labels-selector"; import { useMediaQuery } from "@app/hooks/useMediaQuery"; import { cn } from "@app/lib/cn"; @@ -256,7 +256,7 @@ export default function ResourceLauncher({ (Boolean(defaultViewOverrides.personal) || (isAdmin && Boolean(defaultViewOverrides.orgWide))); - const selectedSites: Selectedsite[] = useMemo( + const selectedSites: SelectedSite[] = useMemo( () => config.siteIds.map((siteId) => ({ siteId, diff --git a/src/components/site-selector.tsx b/src/components/site-selector.tsx index 2a7717572..af918a4fb 100644 --- a/src/components/site-selector.tsx +++ b/src/components/site-selector.tsx @@ -15,7 +15,7 @@ import { CheckIcon } from "lucide-react"; import { useTranslations } from "next-intl"; import { useDebounce } from "use-debounce"; -export type Selectedsite = Pick< +export type SelectedSite = Pick< ListSitesResponse["sites"][number], "name" | "siteId" | "type" > & { @@ -24,8 +24,8 @@ export type Selectedsite = Pick< }; type SiteOnlineStatusProps = { - type: Selectedsite["type"]; - online: Selectedsite["online"]; + type: SelectedSite["type"]; + online: SelectedSite["online"]; }; /** Dot-only indicator matching `SitesTable` colors (newt/wireguard only; nothing for local or missing status). */ @@ -57,8 +57,8 @@ export function SiteOnlineStatus({ type, online }: SiteOnlineStatusProps) { export type SitesSelectorProps = { orgId: string; - selectedSite?: Selectedsite | null; - onSelectSite: (selected: Selectedsite) => void; + selectedSite?: SelectedSite | null; + onSelectSite: (selected: SelectedSite) => void; filterTypes?: string[]; }; @@ -82,7 +82,7 @@ export function SitesSelector({ // always include the selected site in the list of sites shown const sitesShown = useMemo(() => { - const allSites: Array = filterTypes + const allSites: Array = filterTypes ? sites.filter((s) => filterTypes.includes(s.type)) : [...sites]; if ( diff --git a/src/lib/privateResourceUtils.ts b/src/lib/privateResourceUtils.ts index 37cc63756..7d53b5d71 100644 --- a/src/lib/privateResourceUtils.ts +++ b/src/lib/privateResourceUtils.ts @@ -1,11 +1,11 @@ "use client"; -import type { Selectedsite } from "@app/components/site-selector"; +import type { SelectedSite } from "@app/components/site-selector"; import type { SiteResourceData } from "@app/lib/privateResourceForm"; export function buildSelectedSitesForResource( resource: Pick -): Selectedsite[] { +): SelectedSite[] { return resource.siteIds.map((siteId, idx) => ({ name: resource.siteNames[idx] ?? "", siteId,