This commit is contained in:
Fred KISSIE
2026-03-11 04:21:55 +01:00
parent b286096c7b
commit 304ab1964c
3 changed files with 184 additions and 67 deletions

View File

@@ -21,6 +21,14 @@ import { SwitchInput } from "@app/components/SwitchInput";
import { Tag, TagInput } from "@app/components/tags/tag-input"; import { Tag, TagInput } from "@app/components/tags/tag-input";
import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert"; import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert";
import { Button } from "@app/components/ui/button"; import { Button } from "@app/components/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList
} from "@app/components/ui/command";
import { import {
Form, Form,
FormControl, FormControl,
@@ -31,6 +39,11 @@ import {
FormMessage FormMessage
} from "@app/components/ui/form"; } from "@app/components/ui/form";
import { InfoPopup } from "@app/components/ui/info-popup"; import { InfoPopup } from "@app/components/ui/info-popup";
import {
Popover,
PopoverContent,
PopoverTrigger
} from "@app/components/ui/popover";
import { import {
Select, Select,
SelectContent, SelectContent,
@@ -45,19 +58,25 @@ import { usePaidStatus } from "@app/hooks/usePaidStatus";
import { useResourceContext } from "@app/hooks/useResourceContext"; import { useResourceContext } from "@app/hooks/useResourceContext";
import { toast } from "@app/hooks/useToast"; import { toast } from "@app/hooks/useToast";
import { createApiClient, formatAxiosError } from "@app/lib/api"; import { createApiClient, formatAxiosError } from "@app/lib/api";
import { cn } from "@app/lib/cn";
import { getUserDisplayName } from "@app/lib/getUserDisplayName"; import { getUserDisplayName } from "@app/lib/getUserDisplayName";
import { orgQueries, resourceQueries } from "@app/lib/queries"; import {
orgQueries,
resourcePolicyQueries,
resourceQueries
} from "@app/lib/queries";
import { import {
ResourcePolicyContext, ResourcePolicyContext,
ResourcePolicyProvider ResourcePolicyProvider
} from "@app/providers/ResourcePolicyProvider"; } from "@app/providers/ResourcePolicyProvider";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { CaretSortIcon } from "@radix-ui/react-icons";
import { build } from "@server/build"; import { build } from "@server/build";
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, useQueryClient } from "@tanstack/react-query"; import { useQuery, useQueryClient } from "@tanstack/react-query";
import SetResourcePasswordForm from "components/SetResourcePasswordForm"; import SetResourcePasswordForm from "components/SetResourcePasswordForm";
import { Binary, Bot, InfoIcon, Key } from "lucide-react"; import { Binary, Bot, CheckIcon, InfoIcon, Key } from "lucide-react";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { import {
@@ -103,6 +122,41 @@ export default function ResourceAuthenticationPage() {
}) })
); );
const form = useForm({
resolver: zodResolver(resourceTypeSchema),
defaultValues: {
type: "inline"
}
});
const selectedResourceType = useWatch({
control: form.control,
name: "type"
});
const [resourcePolicysearchQuery, setResourcePolicySearchQuery] =
useState("");
const { data: policiesList = [] } = useQuery({
...orgQueries.policies({
orgId: org.org.orgId,
name: resourcePolicysearchQuery
}),
enabled: selectedResourceType === "shared"
});
const { data: sharedPolicy } = useQuery({
...resourcePolicyQueries.single({
resourcePolicyId: resource.resourcePolicyId ?? 1
}),
enabled: !!resource.resourcePolicyId
});
const [selectedPolicy, setSelectedPolicy] = useState<{
name: string;
id: number;
} | null>(null);
const pageLoading = isLoadingPolicies || !defaultPolicy; const pageLoading = isLoadingPolicies || !defaultPolicy;
const [ const [
@@ -127,66 +181,12 @@ export default function ResourceAuthenticationPage() {
} }
]; ];
const form = useForm({
resolver: zodResolver(resourceTypeSchema),
defaultValues: {
type: "inline"
}
});
const selectedResourceType = useWatch({
control: form.control,
name: "type"
});
if (pageLoading) { if (pageLoading) {
return <></>; return <></>;
} }
return ( return (
<> <>
{isSetPasswordOpen && (
<SetResourcePasswordForm
open={isSetPasswordOpen}
setOpen={setIsSetPasswordOpen}
resourceId={resource.resourceId}
onSetPassword={() => {
setIsSetPasswordOpen(false);
updateAuthInfo({
password: true
});
}}
/>
)}
{isSetPincodeOpen && (
<SetResourcePincodeForm
open={isSetPincodeOpen}
setOpen={setIsSetPincodeOpen}
resourceId={resource.resourceId}
onSetPincode={() => {
setIsSetPincodeOpen(false);
updateAuthInfo({
pincode: true
});
}}
/>
)}
{isSetHeaderAuthOpen && (
<SetResourceHeaderAuthForm
open={isSetHeaderAuthOpen}
setOpen={setIsSetHeaderAuthOpen}
resourceId={resource.resourceId}
onSetHeaderAuth={() => {
setIsSetHeaderAuthOpen(false);
updateAuthInfo({
headerAuth: true
});
}}
/>
)}
<SettingsContainer> <SettingsContainer>
<SettingsSection> <SettingsSection>
<SettingsSectionHeader> <SettingsSectionHeader>
@@ -206,20 +206,94 @@ export default function ResourceAuthenticationPage() {
}} }}
cols={2} cols={2}
/> />
{selectedResourceType === "shared" && (
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
className={
"w-full md:w-1/2 justify-between"
// "w-45 justify-between text-sm border-r pr-4 rounded-none h-8 hover:bg-transparent",
// "rounded-l-md rounded-r-xs"
// !proxyTarget.siteId && "text-muted-foreground"
}
>
<span className="truncate max-w-37.5">
{selectedPolicy
? selectedPolicy.name
: t("resourcePolicySelect")}
</span>
<CaretSortIcon className="ml-2h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="p-0 w-45">
<Command shouldFilter={false}>
<CommandInput
placeholder={t("siteSearch")}
value={resourcePolicysearchQuery}
onValueChange={
setResourcePolicySearchQuery
}
/>
<CommandList>
<CommandEmpty>
{t("siteNotFound")}
</CommandEmpty>
<CommandGroup>
{policiesList.map((policy) => (
<CommandItem
key={
policy.resourcePolicyId
}
value={policy.resourcePolicyId.toString()}
onSelect={() =>
setSelectedPolicy({
id: policy.resourcePolicyId,
name: policy.name
})
}
>
<CheckIcon
className={cn(
"mr-2 h-4 w-4",
policy.resourcePolicyId ===
selectedPolicy?.id
? "opacity-100"
: "opacity-0"
)}
/>
{policy.name}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
)}
</SettingsSectionBody> </SettingsSectionBody>
<SettingsSectionFooter> <SettingsSectionFooter className="justify-start">
<Button <Button
type="submit" onClick={() => {
disabled //...
form="policies-type-form" }}
> >
{t("resourceUsersRolesSubmit")} {t("resourcePolicyTypeSave")}
</Button> </Button>
</SettingsSectionFooter> </SettingsSectionFooter>
</SettingsSection> </SettingsSection>
<ResourcePolicyProvider policy={defaultPolicy}> {selectedResourceType === "inline" ? (
<EditPolicyForm hidePolicyNameForm /> <ResourcePolicyProvider policy={defaultPolicy}>
</ResourcePolicyProvider> <EditPolicyForm hidePolicyNameForm />
</ResourcePolicyProvider>
) : (
sharedPolicy && (
<ResourcePolicyProvider policy={sharedPolicy}>
<EditPolicyForm readonly />
</ResourcePolicyProvider>
)
)}
</SettingsContainer> </SettingsContainer>
</> </>
); );

View File

@@ -61,12 +61,19 @@ export function SettingsSectionBody({
} }
export function SettingsSectionFooter({ export function SettingsSectionFooter({
children children,
className
}: { }: {
children: React.ReactNode; children: React.ReactNode;
className?: string;
}) { }) {
return ( return (
<div className="flex flex-col md:flex-row justify-end space-y-2 md:space-y-0 md:space-x-2 mt-auto pt-6"> <div
className={cn(
"flex flex-col md:flex-row justify-end space-y-2 md:space-y-0 md:space-x-2 mt-auto pt-6",
className
)}
>
{children} {children}
</div> </div>
); );

View File

@@ -30,6 +30,7 @@ import z from "zod";
import { remote } from "./api"; import { remote } from "./api";
import { durationToMs } from "./durationToMs"; import { durationToMs } from "./durationToMs";
import { wait } from "./wait"; import { wait } from "./wait";
import type { ListResourcePoliciesResponse } from "@server/routers/resource/types";
export type ProductUpdate = { export type ProductUpdate = {
link: string | null; link: string | null;
@@ -196,6 +197,41 @@ export const orgQueries = {
return res.data.data.resources; return res.data.data.resources;
} }
}),
policies: ({ orgId, name }: { orgId: string; name?: string }) =>
queryOptions({
queryKey: ["ORG", orgId, "RESOURCES_POLICIES", name] as const,
queryFn: async ({ signal, meta }) => {
const sp = new URLSearchParams({
pageSize: "10"
});
if (name) {
sp.set("query", name);
}
const res = await meta!.api.get<
AxiosResponse<ListResourcePoliciesResponse>
>(`/org/${orgId}/resource-policies?${sp.toString()}`, {
signal
});
return res.data.data.policies;
}
})
};
export const resourcePolicyQueries = {
single: ({ resourcePolicyId }: { resourcePolicyId: number }) =>
queryOptions({
queryKey: ["RESOURCE_POLICIES", resourcePolicyId] as const,
queryFn: async ({ signal, meta }) => {
const res = await meta!.api.get<
AxiosResponse<GetDefaultResourcePolicyResponse>
>(`/resource-policy/${resourcePolicyId}`, { signal });
return res.data.data;
}
}) })
}; };
@@ -325,7 +361,7 @@ export const resourceQueries = {
}), }),
defaultPolicy: ({ resourceId }: { resourceId: number }) => defaultPolicy: ({ resourceId }: { resourceId: number }) =>
queryOptions({ queryOptions({
queryKey: ["RESOURCES", resourceId, "POLICIES"] as const, queryKey: ["RESOURCES", resourceId, "DEFAULT_POLICY"] as const,
queryFn: async ({ signal, meta }) => { queryFn: async ({ signal, meta }) => {
const res = await meta!.api.get< const res = await meta!.api.get<
AxiosResponse<GetDefaultResourcePolicyResponse> AxiosResponse<GetDefaultResourcePolicyResponse>