)}
@@ -162,7 +173,7 @@ export function ResourceTargetAddressItem({
{
const input = e.target.value.trim();
const hasProtocol = /^(https?|h2c):\/\//.test(input);
@@ -195,7 +206,7 @@ export function ResourceTargetAddressItem({
}
}}
/>
-
+
{":"}
;
+
+export type SharedPolicySelectorProps = {
+ orgId: string;
+ selectedPolicy: SelectedSharedPolicy | null;
+ onSelectPolicy: (policy: SelectedSharedPolicy | null) => void;
+};
+
+export function SharedPolicySelector({
+ orgId,
+ selectedPolicy,
+ onSelectPolicy
+}: SharedPolicySelectorProps) {
+ const t = useTranslations();
+ const [policySearchQuery, setPolicySearchQuery] = useState("");
+ const [debouncedQuery] = useDebounce(policySearchQuery, 150);
+
+ const { data: policies = [] } = useQuery(
+ orgQueries.policies({
+ orgId,
+ query: debouncedQuery
+ })
+ );
+
+ const policiesShown = useMemo((): SelectedSharedPolicy[] => {
+ const allPolicies: SelectedSharedPolicy[] = policies.map((policy) => ({
+ resourcePolicyId: policy.resourcePolicyId,
+ name: policy.name
+ }));
+ if (
+ debouncedQuery.trim().length === 0 &&
+ selectedPolicy &&
+ !allPolicies.find(
+ (policy) =>
+ policy.resourcePolicyId === selectedPolicy.resourcePolicyId
+ )
+ ) {
+ allPolicies.unshift(selectedPolicy);
+ }
+ return allPolicies;
+ }, [debouncedQuery, policies, selectedPolicy]);
+
+ return (
+
+
+
+ {t("resourcePolicyNotFound")}
+
+ onSelectPolicy(null)}
+ >
+
+
+ {t("none")}
+
+ {t("sharedPolicyNoneDescription")}
+
+
+
+ {policiesShown.map((policy) => (
+
+ onSelectPolicy({
+ resourcePolicyId: policy.resourcePolicyId,
+ name: policy.name
+ })
+ }
+ >
+
+
+ {policy.name}
+
+
+ ))}
+
+
+
+ );
+}
+
+export type SharedPolicySelectProps = {
+ orgId: string;
+ value: number | null;
+ onChange: (value: number | null) => void;
+ className?: string;
+ disabled?: boolean;
+};
+
+export function SharedPolicySelect({
+ orgId,
+ value,
+ onChange,
+ className,
+ disabled
+}: SharedPolicySelectProps) {
+ const t = useTranslations();
+ const [open, setOpen] = useState(false);
+ const [selectedLabel, setSelectedLabel] = useState<{
+ resourcePolicyId: number;
+ name: string;
+ } | null>(null);
+
+ const resolvedLabel =
+ selectedLabel?.resourcePolicyId === value ? selectedLabel.name : null;
+
+ const { data: fetchedPolicy } = useQuery({
+ ...orgQueries.resourcePolicy({
+ resourcePolicyId: value!
+ }),
+ enabled: value !== null && resolvedLabel === null
+ });
+
+ const selectedPolicy = useMemo((): SelectedSharedPolicy | null => {
+ if (value === null) {
+ return null;
+ }
+
+ return {
+ resourcePolicyId: value,
+ name: resolvedLabel ?? fetchedPolicy?.name ?? ""
+ };
+ }, [value, resolvedLabel, fetchedPolicy?.name]);
+
+ const triggerLabel =
+ value === null
+ ? t("none")
+ : (resolvedLabel ??
+ fetchedPolicy?.name ??
+ t("resourcePolicySelect"));
+
+ return (
+
+
+
+
+
+ {
+ onChange(policy?.resourcePolicyId ?? null);
+ setSelectedLabel(
+ policy
+ ? {
+ resourcePolicyId: policy.resourcePolicyId,
+ name: policy.name
+ }
+ : null
+ );
+ setOpen(false);
+ }}
+ />
+
+
+ );
+}
diff --git a/src/components/ui/alert.tsx b/src/components/ui/alert.tsx
index dce25949a..90a87bee6 100644
--- a/src/components/ui/alert.tsx
+++ b/src/components/ui/alert.tsx
@@ -4,7 +4,7 @@ import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@app/lib/cn";
const alertVariants = cva(
- "relative w-full rounded-lg p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
+ "relative w-full rounded-lg p-4 has-[>svg]:grid has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-3 gap-y-1 [&>svg]:col-start-1 [&>svg]:row-start-1 [&>svg]:row-span-full [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:self-center [&>svg]:text-foreground [&>svg~*]:col-start-2",
{
variants: {
variant: {
diff --git a/src/components/ui/data-table-empty-state.tsx b/src/components/ui/data-table-empty-state.tsx
index e7da09f03..e734f7726 100644
--- a/src/components/ui/data-table-empty-state.tsx
+++ b/src/components/ui/data-table-empty-state.tsx
@@ -9,11 +9,13 @@ const PLACEHOLDER_ROW_COUNT = 5;
type DataTableEmptyStateProps = {
colSpan: number;
action?: ReactNode;
+ message?: string;
};
export function DataTableEmptyState({
colSpan,
- action
+ action,
+ message
}: DataTableEmptyStateProps) {
const t = useTranslations();
return (
@@ -32,7 +34,7 @@ export function DataTableEmptyState({
diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx
index 6d065e7aa..c91799712 100644
--- a/src/components/ui/select.tsx
+++ b/src/components/ui/select.tsx
@@ -105,13 +105,17 @@ function SelectLabel({
function SelectItem({
className,
children,
+ description,
...props
-}: React.ComponentProps