fix(modals): consistent text colors and workspace delete confirmation (#4017)

* fix(modals): consistent text colors, copy, and workspace delete confirmation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(modal): replace useEffect with render-time state reset

Replace useEffect anti-pattern for resetting confirmation text with
React's recommended "adjusting state during render" pattern. This
ensures stale text is never painted and avoids an extra render cycle.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Waleed
2026-04-07 11:41:31 -07:00
committed by GitHub
parent 24af61dcbb
commit 04434ddf68
33 changed files with 110 additions and 103 deletions

View File

@@ -1232,10 +1232,8 @@ const DeleteConfirmModal = memo(function DeleteConfirmModal({
<ModalBody>
<p className='text-[var(--text-secondary)]'>
Are you sure you want to delete{' '}
<span className='font-medium text-[var(--text-primary)]'>{fileName}</span>?{' '}
<span className='text-[var(--text-tertiary)]'>
You can restore it from Recently Deleted in Settings.
</span>
<span className='font-medium text-[var(--text-primary)]'>{fileName}</span>? You can
restore it from Recently Deleted in Settings.
</p>
</ModalBody>
<ModalFooter>

View File

@@ -35,8 +35,7 @@ export function DeleteChunkModal({
<ModalHeader>Delete Chunk</ModalHeader>
<ModalBody>
<p className='text-[var(--text-secondary)]'>
Are you sure you want to delete this chunk?{' '}
<span className='text-[var(--text-error)]'>This action cannot be undone.</span>
Are you sure you want to delete this chunk? This action cannot be undone.
</p>
</ModalBody>
<ModalFooter>

View File

@@ -383,7 +383,7 @@ export function DocumentTagsModal({
return (
<Modal open={open} onOpenChange={handleClose}>
<ModalContent size='md'>
<ModalContent size='sm'>
<ModalHeader>
<div className='flex items-center justify-between'>
<span>Document Tags</span>

View File

@@ -1187,7 +1187,7 @@ export function Document({
from future syncs. To temporarily hide it from search, disable it instead.
</span>
) : (
<span className='text-[var(--text-error)]'>This action cannot be undone.</span>
<>This action cannot be undone.</>
)}
</p>
</ModalBody>

View File

@@ -1182,9 +1182,7 @@ export function KnowledgeBase({
The knowledge base and all {pagination.total} document
{pagination.total === 1 ? '' : 's'} within it will be removed.
</span>{' '}
<span className='text-[var(--text-tertiary)]'>
You can restore it from Recently Deleted in Settings.
</span>
You can restore it from Recently Deleted in Settings.
</p>
</ModalBody>
<ModalFooter>
@@ -1221,9 +1219,12 @@ export function KnowledgeBase({
it from future syncs. To temporarily hide it from search, disable it instead.
</span>
) : (
<span className='text-[var(--text-error)]'>
This will permanently delete the document.
</span>
<>
<span className='text-[var(--text-error)]'>
This will permanently delete the document.
</span>{' '}
This action cannot be undone.
</>
)}
</p>
)
@@ -1256,7 +1257,8 @@ export function KnowledgeBase({
<span className='text-[var(--text-error)]'>
This will permanently delete the selected document
{selectedDocuments.size === 1 ? '' : 's'}.
</span>
</span>{' '}
This action cannot be undone.
</p>
</ModalBody>
<ModalFooter>

View File

@@ -261,7 +261,7 @@ export function BaseTagsModal({ open, onOpenChange, knowledgeBaseId }: BaseTagsM
return (
<>
<Modal open={open} onOpenChange={handleClose}>
<ModalContent size='md'>
<ModalContent size='sm'>
<ModalHeader>
<div className='flex items-center justify-between'>
<span>Tags</span>
@@ -421,7 +421,7 @@ export function BaseTagsModal({ open, onOpenChange, knowledgeBaseId }: BaseTagsM
This will remove this tag from {selectedTagUsage?.documentCount || 0} document
{selectedTagUsage?.documentCount !== 1 ? 's' : ''}.
</span>{' '}
<span className='text-[var(--text-error)]'>This action cannot be undone.</span>
This action cannot be undone.
</p>
{selectedTagUsage && selectedTagUsage.documentCount > 0 && (

View File

@@ -1,6 +1,6 @@
'use client'
import { useCallback, useRef, useState } from 'react'
import { useCallback, useState } from 'react'
import { useParams, useRouter } from 'next/navigation'
import { Badge, DocumentAttachment, Tooltip } from '@/components/emcn'
import { formatAbsoluteDate, formatRelativeTime } from '@/lib/core/utils/formatting'
@@ -101,23 +101,6 @@ export function BaseCard({
const [isTagsModalOpen, setIsTagsModalOpen] = useState(false)
const [isDeleting, setIsDeleting] = useState(false)
/**
* Guards against context menu actions triggering card navigation.
* The card's onClick fires synchronously during the click event bubble phase,
* so the ref is checked before the setTimeout-0 callback resets it.
*/
const actionTakenRef = useRef(false)
const withActionGuard = useCallback((fn: () => void) => {
actionTakenRef.current = true
try {
fn()
} finally {
setTimeout(() => {
actionTakenRef.current = false
}, 0)
}
}, [])
const searchParams = new URLSearchParams({
kbName: title,
})
@@ -127,7 +110,7 @@ export function BaseCard({
const handleClick = useCallback(
(e: React.MouseEvent) => {
if (isContextMenuOpen || actionTakenRef.current) {
if (isContextMenuOpen) {
e.preventDefault()
return
}
@@ -147,25 +130,20 @@ export function BaseCard({
)
const handleOpenInNewTab = useCallback(() => {
withActionGuard(() => window.open(href, '_blank'))
}, [href, withActionGuard])
window.open(href, '_blank')
}, [href])
const handleViewTags = useCallback(() => {
withActionGuard(() => setIsTagsModalOpen(true))
}, [withActionGuard])
setIsTagsModalOpen(true)
}, [])
const handleEdit = useCallback(() => {
withActionGuard(() => setIsEditModalOpen(true))
}, [withActionGuard])
setIsEditModalOpen(true)
}, [])
const handleDelete = useCallback(() => {
withActionGuard(() => setIsDeleteModalOpen(true))
}, [withActionGuard])
const handleCopyId = useCallback(() => {
if (!id) return
withActionGuard(() => navigator.clipboard.writeText(id))
}, [id, withActionGuard])
setIsDeleteModalOpen(true)
}, [])
const handleConfirmDelete = useCallback(async () => {
if (!id || !onDelete) return
@@ -262,7 +240,7 @@ export function BaseCard({
onClose={closeContextMenu}
onOpenInNewTab={handleOpenInNewTab}
onViewTags={handleViewTags}
onCopyId={id ? handleCopyId : undefined}
onCopyId={id ? () => navigator.clipboard.writeText(id) : undefined}
onEdit={handleEdit}
onDelete={handleDelete}
showOpenInNewTab={true}

View File

@@ -59,9 +59,7 @@ export const DeleteKnowledgeBaseModal = memo(function DeleteKnowledgeBaseModal({
</span>
</>
)}{' '}
<span className='text-[var(--text-tertiary)]'>
You can restore it from Recently Deleted in Settings.
</span>
You can restore it from Recently Deleted in Settings.
</p>
</ModalBody>
<ModalFooter>

View File

@@ -1267,11 +1267,11 @@ export const NotificationSettings = memo(function NotificationSettings({
<ModalContent size='sm'>
<ModalHeader>Delete Notification</ModalHeader>
<ModalBody>
<p className='text-[var(--text-secondary)] text-caption'>
<p className='text-[var(--text-secondary)]'>
<span className='text-[var(--text-error)]'>
This will permanently remove the notification and stop all deliveries.
</span>{' '}
<span className='text-[var(--text-error)]'>This action cannot be undone.</span>
This action cannot be undone.
</p>
</ModalBody>
<ModalFooter>

View File

@@ -447,12 +447,12 @@ export function ScheduledTasks() {
<ModalContent size='sm'>
<ModalHeader>Delete Scheduled Task</ModalHeader>
<ModalBody>
<p className='text-[var(--text-secondary)] text-caption'>
<p className='text-[var(--text-secondary)]'>
Are you sure you want to delete{' '}
<span className='font-medium text-[var(--text-primary)]'>
{activeTask?.jobTitle || 'this task'}
</span>
? <span className='text-[var(--text-error)]'>This action cannot be undone.</span>
? This action cannot be undone.
</p>
</ModalBody>
<ModalFooter>

View File

@@ -384,7 +384,7 @@ export function ApiKeys() {
<span className='text-[var(--text-error)]'>
will immediately revoke access for any integrations using it.
</span>{' '}
<span className='text-[var(--text-error)]'>This action cannot be undone.</span>
This action cannot be undone.
</p>
</ModalBody>
<ModalFooter>

View File

@@ -415,7 +415,8 @@ export function BYOK() {
API key?{' '}
<span className='text-[var(--text-error)]'>
This workspace will revert to using platform hosted keys.
</span>
</span>{' '}
This action cannot be undone.
</p>
</ModalBody>
<ModalFooter>

View File

@@ -369,7 +369,7 @@ export function Copilot() {
<span className='text-[var(--text-error)]'>
will immediately revoke access for any integrations using it.
</span>{' '}
<span className='text-[var(--text-error)]'>This action cannot be undone.</span>
This action cannot be undone.
</p>
</ModalBody>
<ModalFooter>

View File

@@ -880,7 +880,7 @@ export function CredentialSets() {
<p className='text-[var(--text-secondary)]'>
Are you sure you want to delete{' '}
<span className='font-medium text-[var(--text-primary)]'>{deletingSet?.name}</span>?{' '}
<span className='text-[var(--text-error)]'>This action cannot be undone.</span>
This action cannot be undone.
</p>
</ModalBody>
<ModalFooter>

View File

@@ -192,7 +192,7 @@ export function CustomTools() {
<p className='text-[var(--text-secondary)]'>
Are you sure you want to delete{' '}
<span className='font-medium text-[var(--text-primary)]'>{toolToDelete?.name}</span>?{' '}
<span className='text-[var(--text-error)]'>This action cannot be undone.</span>
This action cannot be undone.
</p>
</ModalBody>
<ModalFooter>

View File

@@ -125,8 +125,8 @@ export function InboxEnableToggle() {
<span className='font-medium text-[var(--text-primary)]'>{config.address}</span>
</>
)}
? Any emails sent to this address after disabling will not be delivered.{' '}
<span className='text-[var(--text-error)]'>This action cannot be undone.</span>
? Any emails sent to this address after disabling will not be delivered. This action
cannot be undone.
</p>
<p className='mt-2 text-[var(--text-secondary)]'>
Your existing conversations and task history will be preserved.

View File

@@ -1136,7 +1136,7 @@ export function IntegrationsManager() {
<span className='font-medium text-[var(--text-primary)]'>
{credentialToDelete?.displayName}
</span>
? <span className='text-[var(--text-error)]'>This action cannot be undone.</span>
? This action cannot be undone.
</p>
{deleteError && (
<div className='mt-3 rounded-lg border border-red-500/50 bg-red-50 p-3 dark:bg-red-950/30'>

View File

@@ -710,7 +710,7 @@ export function MCP({ initialServerId }: MCPProps) {
<p className='text-[var(--text-secondary)]'>
Are you sure you want to delete{' '}
<span className='font-medium text-[var(--text-primary)]'>{serverToDelete?.name}</span>
? <span className='text-[var(--text-error)]'>This action cannot be undone.</span>
? This action cannot be undone.
</p>
</ModalBody>
<ModalFooter>

View File

@@ -173,7 +173,7 @@ export function Skills() {
<p className='text-[var(--text-secondary)]'>
Are you sure you want to delete{' '}
<span className='font-medium text-[var(--text-primary)]'>{skillToDelete?.name}</span>?{' '}
<span className='text-[var(--text-error)]'>This action cannot be undone.</span>
This action cannot be undone.
</p>
</ModalBody>
<ModalFooter>

View File

@@ -46,7 +46,7 @@ export function RemoveMemberDialog({
the team?
</>
)}{' '}
<span className='text-[var(--text-error)]'>This action cannot be undone.</span>
This action cannot be undone.
</p>
{!isSelfRemoval && (

View File

@@ -1111,7 +1111,7 @@ export function WorkflowMcpServers() {
<p className='text-[var(--text-secondary)]'>
Are you sure you want to delete{' '}
<span className='font-medium text-[var(--text-primary)]'>{serverToDelete?.name}</span>
? <span className='text-[var(--text-error)]'>This action cannot be undone.</span>
? This action cannot be undone.
</p>
</ModalBody>
<ModalFooter>

View File

@@ -168,7 +168,7 @@ export function RowModal({ mode, isOpen, onClose, table, row, rowIds, onSuccess
<span className='text-[var(--text-error)]'>
This will permanently remove all data in {isSingleRow ? 'this row' : 'these rows'}.
</span>{' '}
<span className='text-[var(--text-error)]'>This action cannot be undone.</span>
This action cannot be undone.
</p>
</ModalBody>
<ModalFooter>

View File

@@ -1852,9 +1852,7 @@ export function Table({
<span className='text-[var(--text-error)]'>
All {tableData?.rowCount ?? 0} rows will be removed.
</span>{' '}
<span className='text-[var(--text-tertiary)]'>
You can restore it from Recently Deleted in Settings.
</span>
You can restore it from Recently Deleted in Settings.
</p>
</ModalBody>
<ModalFooter>
@@ -1892,7 +1890,7 @@ export function Table({
<span className='text-[var(--text-error)]'>
This will remove all data in this column.
</span>{' '}
<span className='text-[var(--text-error)]'>This action cannot be undone.</span>
This action cannot be undone.
</p>
</ModalBody>
<ModalFooter>

View File

@@ -539,9 +539,7 @@ export function Tables() {
<span className='text-[var(--text-error)]'>
All {activeTable?.rowCount} rows will be removed.
</span>{' '}
<span className='text-[var(--text-tertiary)]'>
You can restore it from Recently Deleted in Settings.
</span>
You can restore it from Recently Deleted in Settings.
</p>
</ModalBody>
<ModalFooter>

View File

@@ -433,7 +433,8 @@ export function ChatDeploy({
<span className='text-[var(--text-error)]'>
This will remove the chat at "{getEmailDomain()}/chat/{existingChat?.identifier}"
and make it unavailable to all users.
</span>
</span>{' '}
This action cannot be undone.
</p>
</ModalBody>
<ModalFooter>

View File

@@ -370,7 +370,7 @@ export function TemplateDeploy({
<span className='font-medium text-[var(--text-primary)]'>
{existingTemplate?.name || formData.name || 'this template'}
</span>
? <span className='text-[var(--text-error)]'>This action cannot be undone.</span>
? This action cannot be undone.
</p>
</ModalBody>
<ModalFooter>

View File

@@ -883,7 +883,8 @@ export function DeployModal({
?{' '}
<span className='text-[var(--text-error)]'>
This will permanently remove the agent configuration.
</span>
</span>{' '}
This action cannot be undone.
</p>
</ModalBody>
<ModalFooter>

View File

@@ -1181,7 +1181,7 @@ try {
This will permanently delete the tool and remove it from any workflows that are
using it.
</span>{' '}
<span className='text-[var(--text-error)]'>This action cannot be undone.</span>
This action cannot be undone.
</p>
</ModalBody>
<ModalFooter>

View File

@@ -897,9 +897,7 @@ export const Panel = memo(function Panel({ workspaceId: propWorkspaceId }: Panel
<span className='text-[var(--text-error)]'>
All associated blocks, executions, and configuration will be removed.
</span>{' '}
<span className='text-[var(--text-tertiary)]'>
You can restore it from Recently Deleted in Settings.
</span>
You can restore it from Recently Deleted in Settings.
</p>
</ModalBody>
<ModalFooter>

View File

@@ -1,5 +1,6 @@
'use client'
import { useState } from 'react'
import { Button, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader } from '@/components/emcn'
interface DeleteModalProps {
@@ -46,11 +47,25 @@ export function DeleteModal({
itemType,
itemName,
}: DeleteModalProps) {
const [confirmationText, setConfirmationText] = useState('')
const [prevIsOpen, setPrevIsOpen] = useState(false)
if (isOpen !== prevIsOpen) {
setPrevIsOpen(isOpen)
if (isOpen) {
setConfirmationText('')
}
}
const isMultiple = Array.isArray(itemName) && itemName.length > 1
const isSingle = !isMultiple
const displayNames = Array.isArray(itemName) ? itemName : itemName ? [itemName] : []
const isWorkspace = itemType === 'workspace'
const workspaceName = isWorkspace && displayNames.length > 0 ? displayNames[0] : ''
const isConfirmed = !isWorkspace || confirmationText === workspaceName
let title = ''
if (itemType === 'workflow') {
title = isMultiple ? 'Delete Workflows' : 'Delete Workflow'
@@ -207,8 +222,8 @@ export function DeleteModal({
Are you sure you want to delete{' '}
<span className='font-medium text-[var(--text-primary)]'>{displayNames[0]}</span>?{' '}
<span className='text-[var(--text-error)]'>
This will permanently remove all associated workflows, folders, logs, and knowledge
bases.
This will permanently remove all associated workflows, tables, files, logs, and
knowledge bases.
</span>
</>
)
@@ -217,33 +232,54 @@ export function DeleteModal({
<>
Are you sure you want to delete this workspace?{' '}
<span className='text-[var(--text-error)]'>
This will permanently remove all associated workflows, folders, logs, and knowledge bases.
This will permanently remove all associated workflows, tables, files, logs, and knowledge
bases.
</span>
</>
)
}
const handleClose = () => {
setConfirmationText('')
onClose()
}
return (
<Modal open={isOpen} onOpenChange={onClose}>
<Modal open={isOpen} onOpenChange={handleClose}>
<ModalContent size='sm'>
<ModalHeader>{title}</ModalHeader>
<ModalBody>
<p className='text-[var(--text-secondary)]'>
{renderDescription()}{' '}
{restorableTypes.has(itemType) ? (
<span className='text-[var(--text-tertiary)]'>
You can restore it from Recently Deleted in Settings.
</span>
) : (
<span className='text-[var(--text-tertiary)]'>This action cannot be undone.</span>
)}
{restorableTypes.has(itemType)
? 'You can restore it from Recently Deleted in Settings.'
: 'This action cannot be undone.'}
</p>
{isWorkspace && workspaceName && (
<div className='mt-3'>
<label
htmlFor='workspace-delete-confirm'
className='mb-1.5 block text-[var(--text-secondary)] text-sm'
>
Type <span className='font-medium text-[var(--text-primary)]'>{workspaceName}</span>{' '}
to confirm
</label>
<input
id='workspace-delete-confirm'
type='text'
value={confirmationText}
onChange={(e) => setConfirmationText(e.target.value)}
className='w-full rounded-md border border-[var(--border)] bg-transparent px-3 py-2 text-[var(--text-primary)] text-sm placeholder:text-[var(--text-tertiary)] focus:border-[var(--border-1)] focus:outline-none'
placeholder={workspaceName}
/>
</div>
)}
</ModalBody>
<ModalFooter>
<Button variant='default' onClick={onClose} disabled={isDeleting}>
<Button variant='default' onClick={handleClose} disabled={isDeleting}>
Cancel
</Button>
<Button variant='destructive' onClick={onConfirm} disabled={isDeleting}>
<Button variant='destructive' onClick={onConfirm} disabled={isDeleting || !isConfirmed}>
{isDeleting ? 'Deleting...' : 'Delete'}
</Button>
</ModalFooter>

View File

@@ -613,8 +613,7 @@ export function InviteModal({ open, onOpenChange, workspaceName }: InviteModalPr
<span className='font-medium text-[var(--text-primary)]'>
{memberToRemove?.email}
</span>{' '}
from this workspace?{' '}
<span className='text-[var(--text-tertiary)]'>This action cannot be undone.</span>
from this workspace? This action cannot be undone.
</p>
</ModalBody>
<ModalFooter>
@@ -646,7 +645,7 @@ export function InviteModal({ open, onOpenChange, workspaceName }: InviteModalPr
<span className='font-medium text-[var(--text-primary)]'>
{invitationToRemove?.email}
</span>
? <span className='text-[var(--text-tertiary)]'>This action cannot be undone.</span>
? This action cannot be undone.
</p>
</ModalBody>
<ModalFooter>

View File

@@ -667,8 +667,8 @@ export function WorkspaceHeader({
<p className='text-[var(--text-secondary)]'>
Are you sure you want to leave{' '}
<span className='font-base text-[var(--text-primary)]'>{leaveTarget?.name}</span>? You
will lose access to all workflows and data in this workspace.{' '}
<span className='text-[var(--text-tertiary)]'>This action cannot be undone.</span>
will lose access to all workflows and data in this workspace. This action cannot be
undone.
</p>
</ModalBody>
<ModalFooter>

View File

@@ -1290,7 +1290,7 @@ export function AccessControl() {
<span className='text-[var(--text-error)]'>
All members will be removed from this group.
</span>{' '}
<span className='text-[var(--text-tertiary)]'>This action cannot be undone.</span>
This action cannot be undone.
</p>
</ModalBody>
<ModalFooter>