Skip to content

Commit 8009ed6

Browse files
committed
Restructure some dialogs to survive the dropdown closing
1 parent 585a174 commit 8009ed6

9 files changed

Lines changed: 279 additions & 115 deletions

File tree

frontend/src/components/pages/agents/list/ai-agent-actions.tsx

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {
2424
import { Label } from 'components/redpanda-ui/components/label';
2525
import { Switch } from 'components/redpanda-ui/components/switch';
2626
import { InlineCode } from 'components/redpanda-ui/components/typography';
27-
import { DeleteResourceAlertDialog } from 'components/ui/delete-resource-alert-dialog';
27+
import { DeleteResourceAlertDialog, DeleteResourceMenuItem } from 'components/ui/delete-resource-alert-dialog';
2828
import { Loader2, MoreHorizontal, Pause, Play } from 'lucide-react';
2929
import { AIAgent_State } from 'protogen/redpanda/api/dataplane/v1alpha3/ai_agent_pb';
3030
import React from 'react';
@@ -48,6 +48,7 @@ export const AIAgentActions = ({ agent, onDeleteWithServiceAccount, isDeletingAg
4848
const { mutate: startAIAgent, isPending: isStarting } = useStartAIAgentMutation();
4949
const { mutate: stopAIAgent, isPending: isStopping } = useStopAIAgentMutation();
5050
const [deleteServiceAccountFlag, setDeleteServiceAccountFlag] = React.useState(false);
51+
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = React.useState(false);
5152

5253
// Get service account and secret info from agent tags
5354
const serviceAccountId = agent.tags?.[CLOUD_MANAGED_TAG_KEYS.SERVICE_ACCOUNT_ID] || null;
@@ -126,33 +127,37 @@ export const AIAgentActions = ({ agent, onDeleteWithServiceAccount, isDeletingAg
126127
</DropdownMenuItem>
127128
)}
128129
{Boolean(canStart || canStop) && <DropdownMenuSeparator />}
129-
<DeleteResourceAlertDialog
130-
isDeleting={isDeletingAgent}
131-
onDelete={handleDelete}
132-
onOpenChange={(open) => {
133-
if (!open) {
134-
setDeleteServiceAccountFlag(false);
135-
}
136-
}}
137-
resourceId={agent.id}
138-
resourceName={agent.name}
139-
resourceType="AI Agent"
140-
>
141-
{Boolean(serviceAccountId && secretName) && (
142-
<div className="flex items-center space-x-2 rounded-lg border border-muted bg-muted/10 p-4">
143-
<Switch
144-
checked={deleteServiceAccountFlag}
145-
id="delete-service-account"
146-
onCheckedChange={setDeleteServiceAccountFlag}
147-
/>
148-
<Label className="cursor-pointer font-normal" htmlFor="delete-service-account">
149-
Also delete associated service account and secret <InlineCode>{secretName}</InlineCode>
150-
</Label>
151-
</div>
152-
)}
153-
</DeleteResourceAlertDialog>
130+
<DeleteResourceMenuItem isDeleting={isDeletingAgent} onSelect={() => setIsDeleteDialogOpen(true)} />
154131
</DropdownMenuContent>
155132
</DropdownMenu>
133+
{/* Render the dialog as a sibling of the dropdown so it survives the menu's unmount on close. */}
134+
<DeleteResourceAlertDialog
135+
isDeleting={isDeletingAgent}
136+
onDelete={handleDelete}
137+
onOpenChange={(next) => {
138+
setIsDeleteDialogOpen(next);
139+
if (!next) {
140+
setDeleteServiceAccountFlag(false);
141+
}
142+
}}
143+
open={isDeleteDialogOpen}
144+
resourceId={agent.id}
145+
resourceName={agent.name}
146+
resourceType="AI Agent"
147+
>
148+
{Boolean(serviceAccountId && secretName) && (
149+
<div className="flex items-center space-x-2 rounded-lg border border-muted bg-muted/10 p-4">
150+
<Switch
151+
checked={deleteServiceAccountFlag}
152+
id="delete-service-account"
153+
onCheckedChange={setDeleteServiceAccountFlag}
154+
/>
155+
<Label className="cursor-pointer font-normal" htmlFor="delete-service-account">
156+
Also delete associated service account and secret <InlineCode>{secretName}</InlineCode>
157+
</Label>
158+
</div>
159+
)}
160+
</DeleteResourceAlertDialog>
156161
</div>
157162
);
158163
};

frontend/src/components/pages/knowledgebase/list/knowledge-base-actions.tsx

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ import {
1616
DropdownMenuSeparator,
1717
DropdownMenuTrigger,
1818
} from 'components/redpanda-ui/components/dropdown-menu';
19-
import { DeleteResourceAlertDialog } from 'components/ui/delete-resource-alert-dialog';
19+
import { DeleteResourceAlertDialog, DeleteResourceMenuItem } from 'components/ui/delete-resource-alert-dialog';
2020
import { MoreHorizontal } from 'lucide-react';
21+
import React from 'react';
2122
import { toast } from 'sonner';
2223

2324
import type { KnowledgeBaseTableRow } from './knowledge-base-list-page';
@@ -33,6 +34,8 @@ export const KnowledgeBaseActionsCell = ({
3334
onDelete,
3435
isDeletingKnowledgeBase,
3536
}: KnowledgeBaseActionsCellProps) => {
37+
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = React.useState(false);
38+
3639
const handleCopySuccess = () => {
3740
toast.success('Retrieval API URL copied to clipboard');
3841
};
@@ -60,15 +63,21 @@ export const KnowledgeBaseActionsCell = ({
6063
Copy URL
6164
</CopyButton>
6265
<DropdownMenuSeparator />
63-
<DeleteResourceAlertDialog
66+
<DeleteResourceMenuItem
6467
isDeleting={isDeletingKnowledgeBase}
65-
onDelete={handleDelete}
66-
resourceId={knowledgeBase.id}
67-
resourceName={knowledgeBase.displayName}
68-
resourceType="Knowledge Base"
68+
onSelect={() => setIsDeleteDialogOpen(true)}
6969
/>
7070
</DropdownMenuContent>
7171
</DropdownMenu>
72+
<DeleteResourceAlertDialog
73+
isDeleting={isDeletingKnowledgeBase}
74+
onDelete={handleDelete}
75+
onOpenChange={setIsDeleteDialogOpen}
76+
open={isDeleteDialogOpen}
77+
resourceId={knowledgeBase.id}
78+
resourceName={knowledgeBase.displayName}
79+
resourceType="Knowledge Base"
80+
/>
7281
</div>
7382
);
7483
};

frontend/src/components/pages/mcp-servers/list/mcp-actions.tsx

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {
2424
import { Label } from 'components/redpanda-ui/components/label';
2525
import { Switch } from 'components/redpanda-ui/components/switch';
2626
import { InlineCode } from 'components/redpanda-ui/components/typography';
27-
import { DeleteResourceAlertDialog } from 'components/ui/delete-resource-alert-dialog';
27+
import { DeleteResourceAlertDialog, DeleteResourceMenuItem } from 'components/ui/delete-resource-alert-dialog';
2828
import { Loader2, MoreHorizontal, Pause, Play } from 'lucide-react';
2929
import React from 'react';
3030
import { MCPServer_State, useStartMCPServerMutation, useStopMCPServerMutation } from 'react-query/api/remote-mcp';
@@ -47,6 +47,7 @@ export const MCPActions = ({ server, onDeleteWithServiceAccount, isDeletingServe
4747
const { mutate: startMCPServer, isPending: isStarting } = useStartMCPServerMutation();
4848
const { mutate: stopMCPServer, isPending: isStopping } = useStopMCPServerMutation();
4949
const [deleteServiceAccountFlag, setDeleteServiceAccountFlag] = React.useState(false);
50+
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = React.useState(false);
5051

5152
// Get service account and secret info from server tags
5253
const serviceAccountId = server.tags?.[CLOUD_MANAGED_TAG_KEYS.SERVICE_ACCOUNT_ID] || null;
@@ -122,33 +123,37 @@ export const MCPActions = ({ server, onDeleteWithServiceAccount, isDeletingServe
122123
</DropdownMenuItem>
123124
)}
124125
{Boolean(canStart || canStop) && <DropdownMenuSeparator />}
125-
<DeleteResourceAlertDialog
126-
isDeleting={isDeletingServer}
127-
onDelete={handleDelete}
128-
onOpenChange={(open) => {
129-
if (!open) {
130-
setDeleteServiceAccountFlag(false);
131-
}
132-
}}
133-
resourceId={server.id}
134-
resourceName={server.name}
135-
resourceType="Remote MCP Server"
136-
>
137-
{Boolean(serviceAccountId && secretName) && (
138-
<div className="flex items-center space-x-2 rounded-lg border border-muted bg-muted/10 p-4">
139-
<Switch
140-
checked={deleteServiceAccountFlag}
141-
id="delete-service-account"
142-
onCheckedChange={setDeleteServiceAccountFlag}
143-
/>
144-
<Label className="cursor-pointer font-normal" htmlFor="delete-service-account">
145-
Also delete associated service account and secret <InlineCode>{secretName}</InlineCode>
146-
</Label>
147-
</div>
148-
)}
149-
</DeleteResourceAlertDialog>
126+
<DeleteResourceMenuItem isDeleting={isDeletingServer} onSelect={() => setIsDeleteDialogOpen(true)} />
150127
</DropdownMenuContent>
151128
</DropdownMenu>
129+
{/* Render the dialog as a sibling of the dropdown so it survives the menu's unmount on close. */}
130+
<DeleteResourceAlertDialog
131+
isDeleting={isDeletingServer}
132+
onDelete={handleDelete}
133+
onOpenChange={(next) => {
134+
setIsDeleteDialogOpen(next);
135+
if (!next) {
136+
setDeleteServiceAccountFlag(false);
137+
}
138+
}}
139+
open={isDeleteDialogOpen}
140+
resourceId={server.id}
141+
resourceName={server.name}
142+
resourceType="Remote MCP Server"
143+
>
144+
{Boolean(serviceAccountId && secretName) && (
145+
<div className="flex items-center space-x-2 rounded-lg border border-muted bg-muted/10 p-4">
146+
<Switch
147+
checked={deleteServiceAccountFlag}
148+
id="delete-service-account"
149+
onCheckedChange={setDeleteServiceAccountFlag}
150+
/>
151+
<Label className="cursor-pointer font-normal" htmlFor="delete-service-account">
152+
Also delete associated service account and secret <InlineCode>{secretName}</InlineCode>
153+
</Label>
154+
</div>
155+
)}
156+
</DeleteResourceAlertDialog>
152157
</div>
153158
);
154159
};

frontend/src/components/pages/rp-connect/pipeline/list.tsx

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ import { Link, List, ListItem, Text } from 'components/redpanda-ui/components/ty
4545
import { createFilterFn } from 'components/redpanda-ui/lib/filter-utils';
4646
import { useDataTableFilter } from 'components/redpanda-ui/lib/use-data-table-filter';
4747
import { cn } from 'components/redpanda-ui/lib/utils';
48-
import { DeleteResourceAlertDialog } from 'components/ui/delete-resource-alert-dialog';
48+
import { DeleteResourceAlertDialog, DeleteResourceMenuItem } from 'components/ui/delete-resource-alert-dialog';
4949
import { PIPELINE_STATE_OPTIONS, STARTABLE_STATES, STOPPABLE_STATES } from 'components/ui/pipeline/constants';
5050
import { isEmbedded, isFeatureFlagEnabled } from 'config';
5151
import { AlertCircle, MoreHorizontal } from 'lucide-react';
@@ -55,7 +55,7 @@ import {
5555
StopPipelineRequestSchema,
5656
} from 'protogen/redpanda/api/console/v1alpha1/pipeline_pb';
5757
import { type Pipeline as APIPipeline, Pipeline_State } from 'protogen/redpanda/api/dataplane/v1/pipeline_pb';
58-
import { memo, useCallback, useMemo } from 'react';
58+
import { memo, useCallback, useMemo, useState } from 'react';
5959
import { useKafkaConnectConnectorsQuery } from 'react-query/api/kafka-connect';
6060
import {
6161
useDeletePipelineMutation,
@@ -201,6 +201,7 @@ const ActionsCell = memo(
201201
const canStop = (STOPPABLE_STATES as readonly Pipeline_State[]).includes(pipeline.state);
202202
const isStarting = pipeline.state === Pipeline_State.STARTING;
203203
const isStopping = pipeline.state === Pipeline_State.STOPPING;
204+
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
204205

205206
const handleStart = () => {
206207
const startRequest = create(StartPipelineRequestSchema, {
@@ -288,19 +289,18 @@ const ActionsCell = memo(
288289
{canStart ? <DropdownMenuItem onClick={handleStart}>Start</DropdownMenuItem> : null}
289290
{canStop ? <DropdownMenuItem onClick={handleStop}>Stop</DropdownMenuItem> : null}
290291
<DropdownMenuSeparator />
291-
<DeleteResourceAlertDialog
292-
buttonIcon={undefined}
293-
buttonText="Delete"
294-
buttonVariant="destructive-ghost"
295-
isDeleting={isDeletingPipeline}
296-
onDelete={handleDelete}
297-
resourceId={pipeline.id}
298-
resourceName={pipeline.name}
299-
resourceType="Pipeline"
300-
triggerVariant="dropdown"
301-
/>
292+
<DeleteResourceMenuItem isDeleting={isDeletingPipeline} onSelect={() => setIsDeleteDialogOpen(true)} />
302293
</DropdownMenuContent>
303294
</DropdownMenu>
295+
<DeleteResourceAlertDialog
296+
isDeleting={isDeletingPipeline}
297+
onDelete={handleDelete}
298+
onOpenChange={setIsDeleteDialogOpen}
299+
open={isDeleteDialogOpen}
300+
resourceId={pipeline.id}
301+
resourceName={pipeline.name}
302+
resourceType="Pipeline"
303+
/>
304304
</div>
305305
);
306306
}

frontend/src/components/pages/secrets-store/secrets-store-actions.tsx

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
DropdownMenuSeparator,
1818
DropdownMenuTrigger,
1919
} from 'components/redpanda-ui/components/dropdown-menu';
20-
import { DeleteResourceAlertDialog } from 'components/ui/delete-resource-alert-dialog';
20+
import { DeleteResourceAlertDialog, DeleteResourceMenuItem } from 'components/ui/delete-resource-alert-dialog';
2121
import { MoreHorizontal, Pencil } from 'lucide-react';
2222
import { useState } from 'react';
2323
import { useListResourcesForSecretQuery } from 'react-query/api/secret';
@@ -82,18 +82,21 @@ export const SecretsStoreActionsCell = ({ secret, onEdit, onDelete, isDeleting }
8282
</div>
8383
</DropdownMenuItem>
8484
<DropdownMenuSeparator />
85-
<DeleteResourceAlertDialog
86-
isDeleting={isDeleting}
87-
onDelete={handleDelete}
88-
onOpenChange={setIsDeleteDialogOpen}
89-
resourceId={secret.id}
90-
resourceName={secret.id}
91-
resourceType="Secret"
92-
>
93-
{resources.length > 0 && <ResourceInUseAlert resources={resources} />}
94-
</DeleteResourceAlertDialog>
85+
<DeleteResourceMenuItem isDeleting={isDeleting} onSelect={() => setIsDeleteDialogOpen(true)} />
9586
</DropdownMenuContent>
9687
</DropdownMenu>
88+
{/* Render the dialog as a sibling of the dropdown so the dialog survives the menu's unmount on close. */}
89+
<DeleteResourceAlertDialog
90+
isDeleting={isDeleting}
91+
onDelete={handleDelete}
92+
onOpenChange={setIsDeleteDialogOpen}
93+
open={isDeleteDialogOpen}
94+
resourceId={secret.id}
95+
resourceName={secret.id}
96+
resourceType="Secret"
97+
>
98+
{resources.length > 0 && <ResourceInUseAlert resources={resources} />}
99+
</DeleteResourceAlertDialog>
97100
</div>
98101
);
99102
};

frontend/src/components/pages/shadowlinks/create/configuration/acls-step.tsx

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,37 @@ import {
3131
import { Tabs, TabsContent, TabsList, TabsTrigger } from 'components/redpanda-ui/components/tabs';
3232
import { ChevronDown, Info, X } from 'lucide-react';
3333
import { ACLOperation, ACLPattern, ACLPermissionType, ACLResource } from 'protogen/redpanda/core/common/v1/acl_pb';
34+
import type React from 'react';
3435
import { useState } from 'react';
3536
import { useFieldArray, useFormContext, useWatch } from 'react-hook-form';
3637

3738
import { ACLFilterResume } from './acl-filter-resume';
39+
import {
40+
getOperationLabel,
41+
getPatternTypeLabel,
42+
getPermissionTypeLabel,
43+
getResourceTypeLabel,
44+
} from '../../shadowlink-helpers';
3845
import type { FormValues } from '../model';
3946

47+
// Base UI's `<Select.Value>` only auto-resolves the selected item's label
48+
// after the popup has been mounted (which registers items into the store).
49+
// While the trigger is closed and the option list has never been opened,
50+
// `Select.Value` falls back to the raw `value` — for our enum-keyed selects
51+
// that means the trigger flashes the numeric enum (e.g. "1") instead of
52+
// "Any". Passing a children render-prop short-circuits that resolution and
53+
// renders the label deterministically, regardless of popup mount state.
54+
const renderResourceTypeLabel = (value: unknown): React.ReactNode =>
55+
value === undefined || value === null || value === '' ? null : getResourceTypeLabel(Number(value) as ACLResource);
56+
const renderPatternTypeLabel = (value: unknown): React.ReactNode =>
57+
value === undefined || value === null || value === '' ? null : getPatternTypeLabel(Number(value) as ACLPattern);
58+
const renderOperationLabel = (value: unknown): React.ReactNode =>
59+
value === undefined || value === null || value === '' ? null : getOperationLabel(Number(value) as ACLOperation);
60+
const renderPermissionTypeLabel = (value: unknown): React.ReactNode =>
61+
value === undefined || value === null || value === ''
62+
? null
63+
: getPermissionTypeLabel(Number(value) as ACLPermissionType);
64+
4065
const allAclsFilter = {
4166
resourceType: ACLResource.ACL_RESOURCE_ANY,
4267
resourcePattern: ACLPattern.ACL_PATTERN_ANY,
@@ -203,7 +228,7 @@ export const AclsStep = () => {
203228
>
204229
<FormControl>
205230
<SelectTrigger>
206-
<SelectValue placeholder="Select type" />
231+
<SelectValue placeholder="Select type">{renderResourceTypeLabel}</SelectValue>
207232
</SelectTrigger>
208233
</FormControl>
209234
<SelectContent>
@@ -245,7 +270,7 @@ export const AclsStep = () => {
245270
>
246271
<FormControl>
247272
<SelectTrigger>
248-
<SelectValue placeholder="Select pattern" />
273+
<SelectValue placeholder="Select pattern">{renderPatternTypeLabel}</SelectValue>
249274
</SelectTrigger>
250275
</FormControl>
251276
<SelectContent>
@@ -313,7 +338,7 @@ export const AclsStep = () => {
313338
>
314339
<FormControl>
315340
<SelectTrigger>
316-
<SelectValue placeholder="Select operation" />
341+
<SelectValue placeholder="Select operation">{renderOperationLabel}</SelectValue>
317342
</SelectTrigger>
318343
</FormControl>
319344
<SelectContent>
@@ -361,7 +386,9 @@ export const AclsStep = () => {
361386
>
362387
<FormControl>
363388
<SelectTrigger>
364-
<SelectValue placeholder="Select permission" />
389+
<SelectValue placeholder="Select permission">
390+
{renderPermissionTypeLabel}
391+
</SelectValue>
365392
</SelectTrigger>
366393
</FormControl>
367394
<SelectContent>

0 commit comments

Comments
 (0)