This commit is contained in:
Fred KISSIE
2026-04-23 06:33:57 +02:00
parent 53c48e6f04
commit b9bee2836b
4 changed files with 135 additions and 54 deletions

View File

@@ -40,7 +40,12 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { tierMatrix } from "@server/lib/billing/tierMatrix"; import { tierMatrix } from "@server/lib/billing/tierMatrix";
import { UserType } from "@server/types/UserTypes"; import { UserType } from "@server/types/UserTypes";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { ChevronsUpDown, ExternalLink } from "lucide-react"; import {
ArrowDownIcon,
ChevronDownIcon,
ChevronsUpDown,
ExternalLink
} from "lucide-react";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
@@ -50,7 +55,7 @@ import {
formatMultiSitesSelectorLabel formatMultiSitesSelectorLabel
} from "./multi-site-selector"; } from "./multi-site-selector";
import type { Selectedsite } from "./site-selector"; import type { Selectedsite } from "./site-selector";
import { CaretSortIcon } from "@radix-ui/react-icons";
import { MachinesSelector } from "./machines-selector"; import { MachinesSelector } from "./machines-selector";
import DomainPicker from "@app/components/DomainPicker"; import DomainPicker from "@app/components/DomainPicker";
import { SwitchInput } from "@app/components/SwitchInput"; import { SwitchInput } from "@app/components/SwitchInput";
@@ -155,7 +160,7 @@ export type InternalResourceData = {
const tagSchema = z.object({ id: z.string(), text: z.string() }); const tagSchema = z.object({ id: z.string(), text: z.string() });
function buildSelectedSitesForResource( function buildSelectedSitesForResource(
resource: InternalResourceData, resource: InternalResourceData
): Selectedsite[] { ): Selectedsite[] {
return resource.siteIds.map((siteId, idx) => ({ return resource.siteIds.map((siteId, idx) => ({
name: resource.siteNames[idx] ?? "", name: resource.siteNames[idx] ?? "",
@@ -608,9 +613,7 @@ export function InternalResourceForm({
users: [], users: [],
clients: [] clients: []
}); });
setSelectedSites( setSelectedSites(buildSelectedSitesForResource(resource));
buildSelectedSitesForResource(resource)
);
setTcpPortMode( setTcpPortMode(
getPortModeFromString(resource.tcpPortRangeString) getPortModeFromString(resource.tcpPortRangeString)
); );
@@ -877,7 +880,9 @@ export function InternalResourceForm({
field.value ?? field.value ??
"http" "http"
} }
disabled={httpSectionDisabled} disabled={
httpSectionDisabled
}
> >
<FormControl> <FormControl>
<SelectTrigger className="w-full"> <SelectTrigger className="w-full">
@@ -918,7 +923,10 @@ export function InternalResourceForm({
<Input <Input
{...field} {...field}
className="w-full" className="w-full"
disabled={isHttpMode && httpSectionDisabled} disabled={
isHttpMode &&
httpSectionDisabled
}
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@@ -974,7 +982,9 @@ export function InternalResourceForm({
field.value ?? field.value ??
"" ""
} }
disabled={httpSectionDisabled} disabled={
httpSectionDisabled
}
onChange={(e) => { onChange={(e) => {
const raw = const raw =
e.target e.target
@@ -1009,7 +1019,9 @@ export function InternalResourceForm({
</div> </div>
{isHttpMode && ( {isHttpMode && (
<PaidFeaturesAlert tiers={tierMatrix.httpPrivateResources} /> <PaidFeaturesAlert
tiers={tierMatrix.httpPrivateResources}
/>
)} )}
{isHttpMode ? ( {isHttpMode ? (
@@ -1022,55 +1034,61 @@ export function InternalResourceForm({
{t(httpConfigurationDescriptionKey)} {t(httpConfigurationDescriptionKey)}
</div> </div>
</div> </div>
<div className={httpSectionDisabled ? "pointer-events-none opacity-50" : undefined}> <div
<DomainPicker className={
key={ httpSectionDisabled
variant === "edit" && siteResourceId ? "pointer-events-none opacity-50"
? `http-domain-${siteResourceId}` : undefined
: "http-domain-create"
} }
orgId={orgId} >
cols={2} <DomainPicker
hideFreeDomain key={
defaultSubdomain={ variant === "edit" && siteResourceId
httpConfigSubdomain ?? undefined ? `http-domain-${siteResourceId}`
} : "http-domain-create"
defaultDomainId={ }
httpConfigDomainId ?? undefined orgId={orgId}
} cols={2}
defaultFullDomain={ hideFreeDomain
httpConfigFullDomain ?? undefined defaultSubdomain={
} httpConfigSubdomain ?? undefined
onDomainChange={(res) => { }
if (res === null) { defaultDomainId={
httpConfigDomainId ?? undefined
}
defaultFullDomain={
httpConfigFullDomain ?? undefined
}
onDomainChange={(res) => {
if (res === null) {
form.setValue(
"httpConfigSubdomain",
null
);
form.setValue(
"httpConfigDomainId",
null
);
form.setValue(
"httpConfigFullDomain",
null
);
return;
}
form.setValue( form.setValue(
"httpConfigSubdomain", "httpConfigSubdomain",
null res.subdomain ?? null
); );
form.setValue( form.setValue(
"httpConfigDomainId", "httpConfigDomainId",
null res.domainId
); );
form.setValue( form.setValue(
"httpConfigFullDomain", "httpConfigFullDomain",
null res.fullDomain
); );
return; }}
} />
form.setValue(
"httpConfigSubdomain",
res.subdomain ?? null
);
form.setValue(
"httpConfigDomainId",
res.domainId
);
form.setValue(
"httpConfigFullDomain",
res.fullDomain
);
}}
/>
</div> </div>
<FormField <FormField
control={form.control} control={form.control}
@@ -1088,7 +1106,9 @@ export function InternalResourceForm({
onCheckedChange={ onCheckedChange={
field.onChange field.onChange
} }
disabled={httpSectionDisabled} disabled={
httpSectionDisabled
}
/> />
</FormControl> </FormControl>
</FormItem> </FormItem>
@@ -1511,7 +1531,7 @@ export function InternalResourceForm({
role="combobox" role="combobox"
className={cn( className={cn(
"justify-between w-full", "justify-between w-full",
"text-muted-foreground pl-1.5" "text-muted-foreground pl-1.5 cursor-text"
)} )}
> >
<span <span
@@ -1548,7 +1568,7 @@ export function InternalResourceForm({
)} )}
</span> </span>
</span> </span>
<CaretSortIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" /> <ChevronDownIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button> </Button>
</FormControl> </FormControl>
</PopoverTrigger> </PopoverTrigger>

View File

@@ -5,7 +5,7 @@ import { useMemo, useState } from "react";
import { useDebounce } from "use-debounce"; import { useDebounce } from "use-debounce";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { MultiSelectTags } from "./multi-select-tags"; import { MultiSelectTags } from "./multi-select/multi-select-tags";
export type SelectedMachine = Pick< export type SelectedMachine = Pick<
ListClientsResponse["clients"][number], ListClientsResponse["clients"][number],

View File

@@ -0,0 +1,61 @@
import {
Popover,
PopoverContent,
PopoverTrigger
} from "@app/components/ui/popover";
import { Button } from "@app/components/ui/button";
import { cn } from "@app/lib/cn";
import { ChevronDownIcon } from "lucide-react";
import {
type TagValue,
type MultiSelectTagsProps,
MultiSelectTags
} from "./multi-select-tags";
export interface MultiSelectInputProps<
T extends TagValue
> extends MultiSelectTagsProps<T> {
buttonText?: string;
}
export function MultiSelectInput<T extends TagValue>({
buttonText,
...props
}: MultiSelectInputProps<T>) {
return (
<Popover>
<PopoverTrigger>
<div
className={cn(
"justify-between w-full",
"text-muted-foreground pl-1.5 cursor-text"
)}
>
<span
className={cn(
"inline-flex items-center gap-1",
"overflow-x-auto"
)}
>
{/* {(field.value ?? []).map((client) => (
<span
key={client.clientId}
className={cn(
"bg-muted-foreground/20 font-normal text-foreground rounded-sm",
"py-1 px-1.5 text-xs"
)}
>
{client.name}
</span>
))} */}
<span className="pl-1 font-normal">{buttonText}</span>
</span>
<ChevronDownIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</div>
</PopoverTrigger>
<PopoverContent className="p-0">
<MultiSelectTags {...props} />
</PopoverContent>
</Popover>
);
}

View File

@@ -6,7 +6,7 @@ import {
CommandInput, CommandInput,
CommandItem, CommandItem,
CommandList CommandList
} from "./ui/command"; } from "../ui/command";
import { cn } from "@app/lib/cn"; import { cn } from "@app/lib/cn";
import { CheckIcon } from "lucide-react"; import { CheckIcon } from "lucide-react";