mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
refactor(emcn): make ChipModal footer/header props-driven and migrate all consumers (#4905)
* refactor(emcn): make ChipModal footer/header props-driven and migrate all consumers
* fix(emcn): restore Cancel disable guard for in-flight ChipModal actions
Add `cancelDisabled` to ChipModalFooter and thread the pre-migration
in-flight guards back into all 45 footers that had them, so destructive
flows can no longer be dismissed mid-mutation.
* feat(emcn): add ChipConfirmModal confirmation primitive
Confirmations have a different button grammar than form modals: a named
dismiss decision ('Keep editing') plus a usually-destructive confirm, with
a single dismiss path — not the structural, never-relabeled Cancel that
ChipModalFooter guarantees. Forcing confirmations through the form footer
produced both the ambiguous-'Cancel' copy loss and the header-X/footer-Cancel
state-reset drift.
ChipConfirmModal models that grammar directly and owns the safety rails every
hand-rolled confirm had to remember: header-X / dismiss button / Esc all route
through onOpenChange (teardown can't desync), and 'pending' disables the
dismiss while the action is in flight. Extract a shared ChipModalFooterShell so
the footer chrome stays a single source of truth across both components.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(emcn): migrate all confirmation dialogs to ChipConfirmModal
Migrate ~30 destructive-confirm and unsaved-changes dialogs across the app
from hand-composed ChipModal + ChipModalFooter to the declarative
ChipConfirmModal. Net effect:
- Unsaved-changes dialogs read 'Keep editing' again (was an ambiguous
'Cancel' after the footer migration) via dismissLabel.
- Header-X and dismiss now share one teardown path, fixing the api-keys /
copilot / scheduled-tasks / base-tags / tables cases where dismissing via
the X left the targeted row selected.
- In-flight 'pending' disables the dismiss button uniformly, so destructive
confirmations can't be dismissed mid-mutation.
- Confirm-modal widths harmonized to 'sm' (several were an oversized 'md').
Form/editor modals and the three-way access-control unsaved dialog keep
ChipModalFooter (structural Cancel is correct there). Also restores the
JSON-cell font size in row-modal and the in-flight Cancel guard in
invite-modal flagged in review.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* style(emcn): normalize confirm pendingLabel ellipsis to '...'
Two dialogs preserved a typographic '…' from their pre-migration copy; the
codebase uses three-dot '...' everywhere else. Align for consistency.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(emcn): restore Enter-to-submit in form modals after footer migration
The props-driven footer renders its primary action as a Chip (type='button'),
so multi-field <form> modals lost implicit Enter submission when the old
type='submit' control was removed. Add a hidden, disabled-mirroring submit
button — the existing codebase idiom (a2a.tsx, mcp.tsx) — to create-base,
row, and help modals so Enter submits exactly as before and still respects
each form's in-flight/validation guard.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: waleed <walif6@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
waleed
parent
7434df9457
commit
4f00baf6b4
@@ -7,7 +7,6 @@ import { Eye, EyeOff } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import {
|
||||
Chip,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalError,
|
||||
@@ -560,24 +559,15 @@ export default function LoginPage({
|
||||
{resetStatus.type === 'error' ? resetStatus.message : null}
|
||||
</ChipModalError>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip
|
||||
variant='filled'
|
||||
flush
|
||||
onClick={() => setForgotPasswordOpen(false)}
|
||||
disabled={isSubmittingReset}
|
||||
>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='primary'
|
||||
flush
|
||||
onClick={handleForgotPassword}
|
||||
disabled={!forgotPasswordEmail || isSubmittingReset}
|
||||
>
|
||||
{isSubmittingReset ? 'Sending…' : 'Send Reset Link'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
<ChipModalFooter
|
||||
onCancel={() => setForgotPasswordOpen(false)}
|
||||
cancelDisabled={isSubmittingReset}
|
||||
primaryAction={{
|
||||
label: isSubmittingReset ? 'Sending…' : 'Send Reset Link',
|
||||
onClick: handleForgotPassword,
|
||||
disabled: !forgotPasswordEmail || isSubmittingReset,
|
||||
}}
|
||||
/>
|
||||
</ChipModal>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -6,6 +6,12 @@ import { cn } from '@/lib/core/utils/cn'
|
||||
import { trackLandingCta } from '@/app/(landing)/landing-analytics'
|
||||
|
||||
interface TemplateCardButtonProps {
|
||||
/**
|
||||
* Curated template prompt, already rewritten to `@`-mention form by the
|
||||
* page's server-side `mentionifyPromptForNames` (registry-free, so the
|
||||
* landing client bundle never pulls the full block registry). Stored verbatim
|
||||
* for the home input to consume after signup.
|
||||
*/
|
||||
prompt: string
|
||||
className?: string
|
||||
children: React.ReactNode
|
||||
|
||||
@@ -87,6 +87,37 @@ function sentenceWithTerminalPunctuation(value: string): string {
|
||||
return /[.!?]$/.test(trimmedValue) ? trimmedValue : `${trimmedValue}.`
|
||||
}
|
||||
|
||||
function escapeRegex(value: string): string {
|
||||
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
}
|
||||
|
||||
/**
|
||||
* Server-side rewrite of bare integration names in a curated template prompt
|
||||
* to `@`-mention form (`Slack` → `@Slack`) so the prompt chips with brand
|
||||
* icons once it is populated into the Mothership home input after signup —
|
||||
* the home auto-mention pipeline only chips token-starting `@` mentions, so
|
||||
* curated prompts must opt in.
|
||||
*
|
||||
* Unlike the workspace surface, which calls `mentionifyIntegrations` from
|
||||
* `@/blocks/integration-matcher`, this runs only over the handful of names a
|
||||
* template actually references (its owner + `otherBlockTypes`) and lives in a
|
||||
* Server Component, so it never pulls the full block/tool/icon registry into
|
||||
* the landing client bundle. Whole-token, longest-first matching with
|
||||
* lookarounds mirrors the canonical matcher; idempotent on already-prefixed
|
||||
* names.
|
||||
*/
|
||||
function mentionifyPromptForNames(prompt: string, names: readonly string[]): string {
|
||||
const unique = [...new Set(names.filter((n) => n.trim().length >= 2))].sort(
|
||||
(a, b) => b.length - a.length
|
||||
)
|
||||
if (unique.length === 0) return prompt
|
||||
const regex = new RegExp(
|
||||
`(?<![A-Za-z0-9_@])(${unique.map(escapeRegex).join('|')})(?![A-Za-z0-9_])`,
|
||||
'gi'
|
||||
)
|
||||
return prompt.replace(regex, (match) => `@${match}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates targeted FAQs from integration metadata.
|
||||
* Questions mirror real search queries to drive FAQPage rich snippets.
|
||||
@@ -652,6 +683,32 @@ export default async function IntegrationPage({ params }: { params: Promise<{ sl
|
||||
...template.otherBlockTypes,
|
||||
]
|
||||
|
||||
const resolveDisplayName = (bt: string): string | null => {
|
||||
const resolvedBt = byType.get(bt)
|
||||
? bt
|
||||
: byType.get(`${bt}_v2`)
|
||||
? `${bt}_v2`
|
||||
: byType.get(`${bt}_v3`)
|
||||
? `${bt}_v3`
|
||||
: bt
|
||||
return byType.get(resolvedBt)?.name ?? null
|
||||
}
|
||||
|
||||
/**
|
||||
* The curated template prompt rewritten so the integrations it
|
||||
* references chip in the home input after signup. Computed
|
||||
* server-side from the template's own integration set — never the
|
||||
* full registry — so the visible card text stays raw while the
|
||||
* stored prompt opts into mention treatment.
|
||||
*/
|
||||
const storedPrompt = (template: (typeof matchingTemplates)[number]) =>
|
||||
mentionifyPromptForNames(
|
||||
template.prompt,
|
||||
resolveTypes(template)
|
||||
.map(resolveDisplayName)
|
||||
.filter((n): n is string => n !== null)
|
||||
)
|
||||
|
||||
const renderIcons = (allTypes: string[]) =>
|
||||
allTypes.map((bt, idx) => {
|
||||
const resolvedBt = byType.get(bt)
|
||||
@@ -698,7 +755,7 @@ export default async function IntegrationPage({ params }: { params: Promise<{ sl
|
||||
{row.map((template) => (
|
||||
<TemplateCardButton
|
||||
key={template.title}
|
||||
prompt={template.prompt}
|
||||
prompt={storedPrompt(template)}
|
||||
className='group flex flex-1 flex-col gap-4 border-[var(--landing-bg-elevated)] border-t p-6 transition-colors first:border-t-0 hover:bg-[var(--landing-bg-elevated)] sm:border-t-0 sm:border-l sm:first:border-l-0'
|
||||
>
|
||||
<div className='flex items-center gap-1.5'>
|
||||
@@ -724,7 +781,7 @@ export default async function IntegrationPage({ params }: { params: Promise<{ sl
|
||||
{lastTemplate && (
|
||||
<>
|
||||
<TemplateCardButton
|
||||
prompt={lastTemplate.prompt}
|
||||
prompt={storedPrompt(lastTemplate)}
|
||||
className='group/link flex items-center gap-4 px-6 py-4 transition-colors hover:bg-[var(--landing-bg-elevated)]'
|
||||
>
|
||||
<div className='flex items-center gap-1.5'>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import { useCallback, useState } from 'react'
|
||||
import {
|
||||
Chip,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalError,
|
||||
@@ -138,32 +137,22 @@ export function RequestIntegrationModal() {
|
||||
)}
|
||||
</ChipModalBody>
|
||||
|
||||
<ChipModalFooter>
|
||||
{status === 'success' ? (
|
||||
<Chip variant='primary' flush onClick={() => handleOpenChange(false)}>
|
||||
Done
|
||||
</Chip>
|
||||
) : (
|
||||
<>
|
||||
<Chip
|
||||
variant='filled'
|
||||
flush
|
||||
onClick={() => setOpen(false)}
|
||||
disabled={status === 'submitting'}
|
||||
>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='primary'
|
||||
flush
|
||||
onClick={handleSubmit}
|
||||
disabled={!canSubmit && status !== 'error'}
|
||||
>
|
||||
{status === 'submitting' ? 'Submitting...' : 'Submit request'}
|
||||
</Chip>
|
||||
</>
|
||||
)}
|
||||
</ChipModalFooter>
|
||||
{status === 'success' ? (
|
||||
<ChipModalFooter
|
||||
onCancel={() => handleOpenChange(false)}
|
||||
primaryAction={{ label: 'Done', onClick: () => handleOpenChange(false) }}
|
||||
/>
|
||||
) : (
|
||||
<ChipModalFooter
|
||||
onCancel={() => setOpen(false)}
|
||||
cancelDisabled={status === 'submitting'}
|
||||
primaryAction={{
|
||||
label: status === 'submitting' ? 'Submitting...' : 'Submit request',
|
||||
onClick: handleSubmit,
|
||||
disabled: !canSubmit && status !== 'error',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</ChipModal>
|
||||
</>
|
||||
)
|
||||
|
||||
+9
-9
@@ -5,7 +5,6 @@ import { createLogger } from '@sim/logger'
|
||||
import { getErrorMessage } from '@sim/utils/errors'
|
||||
import {
|
||||
Badge,
|
||||
Chip,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalError,
|
||||
@@ -442,14 +441,15 @@ export function ConnectOAuthModal(props: ConnectOAuthModalProps) {
|
||||
|
||||
<ChipModalError>{submitError}</ChipModalError>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='ghost' onClick={handleClose} disabled={isPending}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip variant='primary' onClick={handleConnect} disabled={isDisabled}>
|
||||
{isPending ? 'Connecting...' : 'Connect'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
<ChipModalFooter
|
||||
onCancel={handleClose}
|
||||
cancelDisabled={isPending}
|
||||
primaryAction={{
|
||||
label: isPending ? 'Connecting...' : 'Connect',
|
||||
onClick: handleConnect,
|
||||
disabled: isDisabled,
|
||||
}}
|
||||
/>
|
||||
</ChipModal>
|
||||
)
|
||||
}
|
||||
|
||||
+8
-10
@@ -4,7 +4,6 @@ import { useCallback, useMemo, useState } from 'react'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { getErrorMessage } from '@sim/utils/errors'
|
||||
import {
|
||||
Chip,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalField,
|
||||
@@ -154,15 +153,14 @@ export function AddPeopleModal({ credentialId, open, onOpenChange }: AddPeopleMo
|
||||
disabled={isAdding}
|
||||
/>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip
|
||||
variant='primary'
|
||||
onClick={handleAddPeople}
|
||||
disabled={emailsToAdd.length === 0 || isAdding}
|
||||
>
|
||||
{isAdding ? 'Adding...' : 'Add'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
<ChipModalFooter
|
||||
onCancel={handleClose}
|
||||
primaryAction={{
|
||||
label: isAdding ? 'Adding...' : 'Add',
|
||||
onClick: handleAddPeople,
|
||||
disabled: emailsToAdd.length === 0 || isAdding,
|
||||
}}
|
||||
/>
|
||||
</ChipModal>
|
||||
)
|
||||
}
|
||||
|
||||
+10
-17
@@ -1,4 +1,4 @@
|
||||
import { Chip, ChipModal, ChipModalBody, ChipModalFooter, ChipModalHeader } from '@/components/emcn'
|
||||
import { ChipConfirmModal } from '@/components/emcn'
|
||||
|
||||
interface UnsavedChangesModalProps {
|
||||
open: boolean
|
||||
@@ -14,21 +14,14 @@ interface UnsavedChangesModalProps {
|
||||
*/
|
||||
export function UnsavedChangesModal({ open, onOpenChange, onDiscard }: UnsavedChangesModalProps) {
|
||||
return (
|
||||
<ChipModal open={open} onOpenChange={onOpenChange} srTitle='Unsaved Changes'>
|
||||
<ChipModalHeader showDivider={false}>Unsaved Changes</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
You have unsaved changes. Are you sure you want to discard them?
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={() => onOpenChange(false)}>
|
||||
Keep Editing
|
||||
</Chip>
|
||||
<Chip variant='destructive' flush onClick={onDiscard}>
|
||||
Discard Changes
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
<ChipConfirmModal
|
||||
open={open}
|
||||
onOpenChange={onOpenChange}
|
||||
srTitle='Unsaved Changes'
|
||||
title='Unsaved Changes'
|
||||
description='You have unsaved changes. Are you sure you want to discard them?'
|
||||
dismissLabel='Keep editing'
|
||||
confirm={{ label: 'Discard Changes', onClick: onDiscard }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import { GitBranch } from 'lucide-react'
|
||||
import { useParams, useRouter } from 'next/navigation'
|
||||
import {
|
||||
Check,
|
||||
Chip,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalField,
|
||||
@@ -279,14 +278,13 @@ export const MessageActions = memo(function MessageActions({
|
||||
}
|
||||
/>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={() => handleModalClose(false)}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip variant='primary' flush onClick={handleSubmitFeedback}>
|
||||
Submit
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
<ChipModalFooter
|
||||
onCancel={() => handleModalClose(false)}
|
||||
primaryAction={{
|
||||
label: 'Submit',
|
||||
onClick: handleSubmitFeedback,
|
||||
}}
|
||||
/>
|
||||
</ChipModal>
|
||||
</>
|
||||
)
|
||||
|
||||
+17
-18
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { memo } from 'react'
|
||||
import { Chip, ChipModal, ChipModalBody, ChipModalFooter, ChipModalHeader } from '@/components/emcn'
|
||||
import { ChipConfirmModal } from '@/components/emcn'
|
||||
|
||||
interface DeleteConfirmModalProps {
|
||||
open: boolean
|
||||
@@ -34,12 +34,13 @@ export const DeleteConfirmModal = memo(function DeleteConfirmModal({
|
||||
: 'You can restore it from Recently Deleted in Settings.'
|
||||
|
||||
return (
|
||||
<ChipModal open={open} onOpenChange={onOpenChange} srTitle={title}>
|
||||
<ChipModalHeader onClose={() => onOpenChange(false)} showDivider={false}>
|
||||
{title}
|
||||
</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
<ChipConfirmModal
|
||||
open={open}
|
||||
onOpenChange={onOpenChange}
|
||||
srTitle={title}
|
||||
title={title}
|
||||
description={
|
||||
<>
|
||||
Are you sure you want to delete{' '}
|
||||
{fileName ? (
|
||||
<span className='font-medium text-[var(--text-primary)]'>{fileName}</span>
|
||||
@@ -47,16 +48,14 @@ export const DeleteConfirmModal = memo(function DeleteConfirmModal({
|
||||
`${totalCount} item${totalCount === 1 ? '' : 's'}`
|
||||
)}
|
||||
? {consequence}
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={() => onOpenChange(false)} disabled={isPending}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip variant='destructive' flush onClick={onDelete} disabled={isPending}>
|
||||
{isPending ? 'Deleting...' : 'Delete'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
</>
|
||||
}
|
||||
confirm={{
|
||||
label: 'Delete',
|
||||
onClick: onDelete,
|
||||
pending: isPending,
|
||||
pendingLabel: 'Deleting...',
|
||||
}}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
@@ -7,12 +7,8 @@ import { useParams, useRouter, useSearchParams } from 'next/navigation'
|
||||
import { usePostHog } from 'posthog-js/react'
|
||||
import {
|
||||
Button,
|
||||
Chip,
|
||||
ChipCombobox,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalFooter,
|
||||
ChipModalHeader,
|
||||
ChipConfirmModal,
|
||||
Columns2,
|
||||
type ComboboxOption,
|
||||
Eye,
|
||||
@@ -1873,26 +1869,15 @@ export function Files() {
|
||||
saveRef={saveRef}
|
||||
/>
|
||||
|
||||
<ChipModal
|
||||
<ChipConfirmModal
|
||||
open={showUnsavedChangesAlert}
|
||||
onOpenChange={setShowUnsavedChangesAlert}
|
||||
srTitle='Unsaved Changes'
|
||||
>
|
||||
<ChipModalHeader showDivider={false}>Unsaved Changes</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
You have unsaved changes. Are you sure you want to discard them?
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={() => setShowUnsavedChangesAlert(false)}>
|
||||
Keep Editing
|
||||
</Chip>
|
||||
<Chip variant='destructive' flush onClick={handleDiscardChanges}>
|
||||
Discard Changes
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
title='Unsaved Changes'
|
||||
description='You have unsaved changes. Are you sure you want to discard them?'
|
||||
dismissLabel='Keep editing'
|
||||
confirm={{ label: 'Discard Changes', onClick: handleDiscardChanges }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<DeleteConfirmModal
|
||||
|
||||
+1
-16
@@ -24,27 +24,12 @@ import {
|
||||
import { captureEvent } from '@/lib/posthog/client'
|
||||
import { ConnectOAuthModal } from '@/app/workspace/[workspaceId]/components/connect-oauth-modal'
|
||||
import { getBareIconStyle } from '@/blocks/icon-color'
|
||||
import { getIntegrationMatcher } from '@/blocks/integration-matcher'
|
||||
import type { ModuleTag } from '@/blocks/types'
|
||||
import { useWorkspaceCredentials } from '@/hooks/queries/credentials'
|
||||
import { useKnowledgeBasesQuery } from '@/hooks/queries/kb/knowledge'
|
||||
import { useOAuthConnections } from '@/hooks/queries/oauth/oauth-connections'
|
||||
import { useTablesList } from '@/hooks/queries/tables'
|
||||
|
||||
/**
|
||||
* Rewrites bare integration names in `text` to `@`-mention form (`Kalshi` →
|
||||
* `@Kalshi`) so they chip when the prompt is populated into the input — the
|
||||
* auto-mention pipeline deliberately ignores un-prefixed names, so curated
|
||||
* prompts opt in here. Idempotent: names already prefixed are left untouched.
|
||||
*/
|
||||
function mentionifyIntegrations(text: string): string {
|
||||
const { regex } = getIntegrationMatcher()
|
||||
if (!regex || !text) return text
|
||||
return text.replace(regex, (match: string, _name: string, offset: number) =>
|
||||
offset > 0 && text[offset - 1] === '@' ? match : `@${match}`
|
||||
)
|
||||
}
|
||||
|
||||
type Icon = ComponentType<{ className?: string; style?: CSSProperties }>
|
||||
|
||||
type Action =
|
||||
@@ -121,7 +106,7 @@ const CANDIDATES: readonly Candidate[] = (() => {
|
||||
id: `${blockType}-${i}`,
|
||||
blockType,
|
||||
label: template.title,
|
||||
prompt: mentionifyIntegrations(template.prompt),
|
||||
prompt: template.prompt,
|
||||
icon: template.icon as Icon,
|
||||
modules: template.modules,
|
||||
featured: template.featured ?? false,
|
||||
|
||||
+19
-14
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import { Mic } from '@/components/emcn'
|
||||
import { Mic, Tooltip } from '@/components/emcn'
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
|
||||
interface MicButtonProps {
|
||||
@@ -11,18 +11,23 @@ interface MicButtonProps {
|
||||
|
||||
export const MicButton = React.memo(function MicButton({ isListening, onToggle }: MicButtonProps) {
|
||||
return (
|
||||
<button
|
||||
type='button'
|
||||
onClick={onToggle}
|
||||
className={cn(
|
||||
'flex h-[28px] w-[28px] items-center justify-center rounded-full transition-colors',
|
||||
isListening
|
||||
? 'bg-red-500 text-white hover:bg-red-600'
|
||||
: 'text-[var(--text-icon)] hover:bg-[#F7F7F7] dark:hover:bg-[#303030]'
|
||||
)}
|
||||
title={isListening ? 'Stop listening' : 'Voice input'}
|
||||
>
|
||||
<Mic className='h-[16px] w-[16px]' />
|
||||
</button>
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
<button
|
||||
type='button'
|
||||
onClick={onToggle}
|
||||
aria-label={isListening ? 'Stop listening' : 'Voice input'}
|
||||
className={cn(
|
||||
'flex h-[28px] w-[28px] items-center justify-center rounded-full transition-colors',
|
||||
isListening
|
||||
? 'bg-red-500 text-white hover:bg-red-600'
|
||||
: 'text-[var(--text-icon)] hover:bg-[#F7F7F7] dark:hover:bg-[#303030]'
|
||||
)}
|
||||
>
|
||||
<Mic className='h-[16px] w-[16px]' />
|
||||
</button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='top'>{isListening ? 'Stop listening' : 'Voice input'}</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
)
|
||||
})
|
||||
|
||||
@@ -63,6 +63,7 @@ import {
|
||||
SKILL_CHIP_TRIGGER,
|
||||
stripMentionTrigger,
|
||||
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/user-input/utils'
|
||||
import { mentionifyIntegrations } from '@/blocks/integration-matcher'
|
||||
import { type SkillDefinition, useSkills } from '@/hooks/queries/skills'
|
||||
import { useSettingsNavigation } from '@/hooks/use-settings-navigation'
|
||||
import { useSpeechToText } from '@/hooks/use-speech-to-text'
|
||||
@@ -138,9 +139,11 @@ interface UserInputProps {
|
||||
|
||||
export interface UserInputHandle {
|
||||
loadQueuedMessage: (msg: QueuedMessage) => void
|
||||
/** Populates the textarea with `text` (running it through auto-mention
|
||||
* chipification), focuses the input, and places the caret at the end.
|
||||
* Does NOT submit. Safe to call with the same text twice in a row. */
|
||||
/** Populates the textarea with a CURATED prompt (suggested action, template,
|
||||
* etc. — never free-form user prose), running it through `mentionifyIntegrations`
|
||||
* (bare `Slack` → `@Slack`) and then auto-mention chipification so integration
|
||||
* names chip with brand icons. Focuses the input and places the caret at the
|
||||
* end. Does NOT submit. Safe to call with the same text twice in a row. */
|
||||
populatePrompt: (text: string) => void
|
||||
}
|
||||
|
||||
@@ -376,6 +379,12 @@ export const UserInput = forwardRef<UserInputHandle, UserInputProps>(function Us
|
||||
* mounted. Mirrors the previously inline render-phase derivation but
|
||||
* now runs the prompt through `applyToText` so integration `@`-mentions
|
||||
* get chipified consistently with paste / draft restore flows.
|
||||
*
|
||||
* Deliberately does NOT run `mentionifyIntegrations` here: `defaultValue` is
|
||||
* seeded from `LandingPromptStorage`, whose producers include the free-form
|
||||
* landing prompt panel as well as curated CTAs. Curated producers opt their
|
||||
* bare names in at the store seam (`storeCuratedPrompt`), so prose seeded here
|
||||
* is never bare-chipped (the scunthorpe constraint).
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (defaultValue === prevDefaultValueRef.current) return
|
||||
@@ -464,7 +473,12 @@ export const UserInput = forwardRef<UserInputHandle, UserInputProps>(function Us
|
||||
focusTextareaAtEnd()
|
||||
},
|
||||
populatePrompt: (text: string) => {
|
||||
setValue(applyAutoMentions(text))
|
||||
// `text` is a curated prompt, so opt its bare integration names into
|
||||
// `@`-mention form before chipification (the auto-mention pipeline only
|
||||
// chips already-`@`-prefixed names). Curated prompts arriving via the
|
||||
// `defaultValue` seed are mentionified at their producer instead, since
|
||||
// that path is also reused for free-form landing prose.
|
||||
setValue(applyAutoMentions(mentionifyIntegrations(text)))
|
||||
focusTextareaAtEnd()
|
||||
},
|
||||
}),
|
||||
|
||||
+3
-3
@@ -5,7 +5,6 @@ import { ArrowLeft, ArrowRight, Plus } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
|
||||
import { Chip, ChipDropdown, ChipLink } from '@/components/emcn'
|
||||
import { LandingPromptStorage } from '@/lib/core/utils/browser-storage'
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
import {
|
||||
blockTypeToIconMap,
|
||||
@@ -22,6 +21,7 @@ import {
|
||||
CONNECT_MODE,
|
||||
CONNECT_QUERY_PARAM,
|
||||
} from '@/app/workspace/[workspaceId]/integrations/connect-route'
|
||||
import { storeCuratedPrompt } from '@/blocks/integration-matcher'
|
||||
import {
|
||||
getSuggestedSkillsForBlock,
|
||||
getTemplatesForBlock,
|
||||
@@ -117,7 +117,7 @@ export function IntegrationBlockDetail({ integration, workspaceId }: Integration
|
||||
}
|
||||
|
||||
const handleAddInChat = () => {
|
||||
LandingPromptStorage.store(`Explore ${integration.name}. What can I do?`)
|
||||
storeCuratedPrompt(`Explore ${integration.name}. What can I do?`)
|
||||
router.push(`/workspace/${workspaceId}/home`)
|
||||
}
|
||||
|
||||
@@ -247,7 +247,7 @@ function TemplatesSection({ integration, templates, workspaceId }: TemplatesSect
|
||||
const router = useRouter()
|
||||
|
||||
const handleSelect = (prompt: string) => {
|
||||
LandingPromptStorage.store(prompt)
|
||||
storeCuratedPrompt(prompt)
|
||||
router.push(`/workspace/${workspaceId}/home`)
|
||||
}
|
||||
|
||||
|
||||
+16
-17
@@ -4,7 +4,6 @@ import { type ComponentType, useEffect, useState } from 'react'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { getErrorMessage } from '@sim/utils/errors'
|
||||
import {
|
||||
Chip,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalError,
|
||||
@@ -276,14 +275,14 @@ function GoogleServiceAccountModal({
|
||||
|
||||
<ChipModalError>{error}</ChipModalError>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='ghost' onClick={() => onOpenChange(false)}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip variant='primary' onClick={handleSubmit} disabled={isDisabled}>
|
||||
{isPending ? 'Adding...' : 'Add service account'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
<ChipModalFooter
|
||||
onCancel={() => onOpenChange(false)}
|
||||
primaryAction={{
|
||||
label: isPending ? 'Adding...' : 'Add service account',
|
||||
onClick: handleSubmit,
|
||||
disabled: isDisabled,
|
||||
}}
|
||||
/>
|
||||
</ChipModal>
|
||||
)
|
||||
}
|
||||
@@ -409,14 +408,14 @@ function AtlassianServiceAccountModal({
|
||||
|
||||
<ChipModalError>{error}</ChipModalError>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='ghost' onClick={() => onOpenChange(false)}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip variant='primary' onClick={handleSubmit} disabled={isDisabled}>
|
||||
{isPending ? 'Adding...' : 'Add service account'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
<ChipModalFooter
|
||||
onCancel={() => onOpenChange(false)}
|
||||
primaryAction={{
|
||||
label: isPending ? 'Adding...' : 'Add service account',
|
||||
onClick: handleSubmit,
|
||||
disabled: isDisabled,
|
||||
}}
|
||||
/>
|
||||
</ChipModal>
|
||||
)
|
||||
}
|
||||
|
||||
+9
-4
@@ -3,18 +3,23 @@
|
||||
import { ArrowRight } from 'lucide-react'
|
||||
import { useParams, useRouter } from 'next/navigation'
|
||||
import { Chip } from '@/components/emcn'
|
||||
import { LandingPromptStorage } from '@/lib/core/utils/browser-storage'
|
||||
import { IntegrationsShowcase } from '@/app/workspace/[workspaceId]/integrations/components/integrations-showcase'
|
||||
import { storeCuratedPrompt } from '@/blocks/integration-matcher'
|
||||
|
||||
interface ShowcaseWithExploreProps {
|
||||
/** Prompt stored for the home page chat to consume after navigation. */
|
||||
/**
|
||||
* Prompt stored for the home page chat to consume after navigation. Bare
|
||||
* integration names are rewritten to `@`-mention form on store so they chip
|
||||
* in the chat input (mention treatment is opt-in there).
|
||||
*/
|
||||
prompt: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the integrations showcase with an "Explore in chat" CTA pinned into
|
||||
* the showcase's bottom-right mask notch. Clicking the CTA seeds the home page
|
||||
* chat with `prompt` and navigates to the workspace home.
|
||||
* chat with `prompt` (via {@link storeCuratedPrompt} so integration names chip)
|
||||
* and navigates to the workspace home.
|
||||
*/
|
||||
export function ShowcaseWithExplore({ prompt }: ShowcaseWithExploreProps) {
|
||||
const params = useParams()
|
||||
@@ -28,7 +33,7 @@ export function ShowcaseWithExplore({ prompt }: ShowcaseWithExploreProps) {
|
||||
variant='filled'
|
||||
rightIcon={ArrowRight}
|
||||
onClick={() => {
|
||||
LandingPromptStorage.store(prompt)
|
||||
storeCuratedPrompt(prompt)
|
||||
router.push(`/workspace/${workspaceId}/home`)
|
||||
}}
|
||||
className='absolute right-0 bottom-0 mx-0'
|
||||
|
||||
+14
-27
@@ -6,12 +6,9 @@ import { getErrorMessage } from '@sim/utils/errors'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import {
|
||||
Chip,
|
||||
ChipConfirmModal,
|
||||
ChipInput,
|
||||
ChipLink,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalFooter,
|
||||
ChipModalHeader,
|
||||
ChipTextarea,
|
||||
Send,
|
||||
toast,
|
||||
@@ -292,35 +289,25 @@ export function ConnectedCredentialDetail({
|
||||
<CredentialMembersSection credentialId={credential.id} isAdmin={isAdmin} />
|
||||
</CredentialDetailLayout>
|
||||
|
||||
<ChipModal
|
||||
<ChipConfirmModal
|
||||
open={showDeleteConfirmDialog}
|
||||
onOpenChange={setShowDeleteConfirmDialog}
|
||||
srTitle='Disconnect Integration'
|
||||
>
|
||||
<ChipModalHeader showDivider={false}>Disconnect Integration</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
title='Disconnect Integration'
|
||||
description={
|
||||
<>
|
||||
Are you sure you want to disconnect{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>{credential.displayName}</span>
|
||||
? This action cannot be undone.
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={() => setShowDeleteConfirmDialog(false)}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='destructive'
|
||||
flush
|
||||
onClick={handleConfirmDelete}
|
||||
disabled={disconnectOAuthService.isPending || deleteCredential.isPending}
|
||||
>
|
||||
{disconnectOAuthService.isPending || deleteCredential.isPending
|
||||
? 'Disconnecting...'
|
||||
: 'Disconnect'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
</>
|
||||
}
|
||||
confirm={{
|
||||
label: 'Disconnect',
|
||||
onClick: handleConfirmDelete,
|
||||
pending: disconnectOAuthService.isPending || deleteCredential.isPending,
|
||||
pendingLabel: 'Disconnecting...',
|
||||
}}
|
||||
/>
|
||||
|
||||
<AddPeopleModal
|
||||
credentialId={credential.id}
|
||||
|
||||
+14
-19
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { Chip, ChipModal, ChipModalBody, ChipModalFooter, ChipModalHeader } from '@/components/emcn'
|
||||
import { ChipConfirmModal } from '@/components/emcn'
|
||||
import type { ChunkData } from '@/lib/knowledge/types'
|
||||
import { useDeleteChunk } from '@/hooks/queries/kb/knowledge'
|
||||
|
||||
@@ -30,23 +30,18 @@ export function DeleteChunkModal({
|
||||
if (!chunk) return null
|
||||
|
||||
return (
|
||||
<ChipModal open={isOpen} onOpenChange={onClose} srTitle='Delete Chunk'>
|
||||
<ChipModalHeader onClose={onClose} showDivider={false}>
|
||||
Delete Chunk
|
||||
</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
Are you sure you want to delete this chunk? This action cannot be undone.
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush disabled={isDeleting} onClick={onClose}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip variant='destructive' flush onClick={handleDeleteChunk} disabled={isDeleting}>
|
||||
{isDeleting ? <>Deleting…</> : <>Delete</>}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
<ChipConfirmModal
|
||||
open={isOpen}
|
||||
onOpenChange={onClose}
|
||||
srTitle='Delete Chunk'
|
||||
title='Delete Chunk'
|
||||
description='Are you sure you want to delete this chunk? This action cannot be undone.'
|
||||
confirm={{
|
||||
label: 'Delete',
|
||||
onClick: handleDeleteChunk,
|
||||
pending: isDeleting,
|
||||
pendingLabel: 'Deleting...',
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
+4
-9
@@ -5,7 +5,6 @@ import { createLogger } from '@sim/logger'
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
Chip,
|
||||
ChipCombobox,
|
||||
ChipInput,
|
||||
ChipModal,
|
||||
@@ -16,7 +15,6 @@ import {
|
||||
Label,
|
||||
Trash,
|
||||
} from '@/components/emcn'
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
import { handleKeyboardActivation } from '@/lib/core/utils/keyboard'
|
||||
import { ALL_TAG_SLOTS, type AllTagSlot, MAX_TAG_SLOTS } from '@/lib/knowledge/constants'
|
||||
import type { DocumentTag } from '@/lib/knowledge/tags/types'
|
||||
@@ -459,7 +457,6 @@ export function DocumentTagsModal({
|
||||
}}
|
||||
placeholder='Enter or select tag name'
|
||||
editable={true}
|
||||
className={cn(tagNameConflict && 'border-[var(--text-error)]')}
|
||||
/>
|
||||
) : (
|
||||
<ChipInput
|
||||
@@ -612,7 +609,6 @@ export function DocumentTagsModal({
|
||||
}}
|
||||
placeholder='Enter or select tag name'
|
||||
editable={true}
|
||||
className={cn(tagNameConflict && 'border-[var(--text-error)]')}
|
||||
/>
|
||||
) : (
|
||||
<ChipInput
|
||||
@@ -745,11 +741,10 @@ export function DocumentTagsModal({
|
||||
</div>
|
||||
</ChipModalBody>
|
||||
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={() => handleClose(false)}>
|
||||
Close
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
<ChipModalFooter
|
||||
onCancel={() => handleClose(false)}
|
||||
primaryAction={{ label: 'Close', onClick: () => handleClose(false) }}
|
||||
/>
|
||||
</ChipModal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,16 +4,7 @@ import { useCallback, useEffect, useEffectEvent, useMemo, useRef, useState } fro
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { ChevronDown, ChevronUp, FileText, Pencil, Tag } from 'lucide-react'
|
||||
import { useParams, useRouter, useSearchParams } from 'next/navigation'
|
||||
import {
|
||||
Badge,
|
||||
Chip,
|
||||
ChipCombobox,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalFooter,
|
||||
ChipModalHeader,
|
||||
Trash,
|
||||
} from '@/components/emcn'
|
||||
import { Badge, ChipCombobox, ChipConfirmModal, Trash } from '@/components/emcn'
|
||||
import { Database } from '@/components/emcn/icons'
|
||||
import { SearchHighlight } from '@/components/ui/search-highlight'
|
||||
import type { ChunkData } from '@/lib/knowledge/types'
|
||||
@@ -64,33 +55,20 @@ type SaveStatus = 'idle' | 'saving' | 'saved' | 'error'
|
||||
interface UnsavedChangesModalProps {
|
||||
open: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
onKeepEditing: () => void
|
||||
onDiscard: () => void
|
||||
}
|
||||
|
||||
function UnsavedChangesModal({
|
||||
open,
|
||||
onOpenChange,
|
||||
onKeepEditing,
|
||||
onDiscard,
|
||||
}: UnsavedChangesModalProps) {
|
||||
function UnsavedChangesModal({ open, onOpenChange, onDiscard }: UnsavedChangesModalProps) {
|
||||
return (
|
||||
<ChipModal open={open} onOpenChange={onOpenChange} srTitle='Unsaved Changes'>
|
||||
<ChipModalHeader showDivider={false}>Unsaved Changes</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
You have unsaved changes. Are you sure you want to discard them?
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={onKeepEditing}>
|
||||
Keep Editing
|
||||
</Chip>
|
||||
<Chip variant='destructive' flush onClick={onDiscard}>
|
||||
Discard Changes
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
<ChipConfirmModal
|
||||
open={open}
|
||||
onOpenChange={onOpenChange}
|
||||
srTitle='Unsaved Changes'
|
||||
title='Unsaved Changes'
|
||||
description='You have unsaved changes. Are you sure you want to discard them?'
|
||||
dismissLabel='Keep editing'
|
||||
confirm={{ label: 'Discard Changes', onClick: onDiscard }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -360,6 +338,13 @@ export function Document({
|
||||
}
|
||||
}, [isDirty, isCreatingNewChunk])
|
||||
|
||||
const handleUnsavedChangesOpenChange = useCallback((open: boolean) => {
|
||||
if (!open) {
|
||||
setShowUnsavedChangesAlert(false)
|
||||
setPendingAction(null)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const handleDiscardChanges = useCallback(() => {
|
||||
setShowUnsavedChangesAlert(false)
|
||||
const action = pendingAction
|
||||
@@ -1095,11 +1080,7 @@ export function Document({
|
||||
|
||||
<UnsavedChangesModal
|
||||
open={showUnsavedChangesAlert}
|
||||
onOpenChange={setShowUnsavedChangesAlert}
|
||||
onKeepEditing={() => {
|
||||
setShowUnsavedChangesAlert(false)
|
||||
setPendingAction(null)
|
||||
}}
|
||||
onOpenChange={handleUnsavedChangesOpenChange}
|
||||
onDiscard={handleDiscardChanges}
|
||||
/>
|
||||
</>
|
||||
@@ -1141,11 +1122,7 @@ export function Document({
|
||||
|
||||
<UnsavedChangesModal
|
||||
open={showUnsavedChangesAlert}
|
||||
onOpenChange={setShowUnsavedChangesAlert}
|
||||
onKeepEditing={() => {
|
||||
setShowUnsavedChangesAlert(false)
|
||||
setPendingAction(null)
|
||||
}}
|
||||
onOpenChange={handleUnsavedChangesOpenChange}
|
||||
onDiscard={handleDiscardChanges}
|
||||
/>
|
||||
</>
|
||||
@@ -1201,14 +1178,13 @@ export function Document({
|
||||
isLoading={isBulkOperating}
|
||||
/>
|
||||
|
||||
<ChipModal
|
||||
<ChipConfirmModal
|
||||
open={showDeleteDocumentDialog}
|
||||
onOpenChange={setShowDeleteDocumentDialog}
|
||||
srTitle='Delete Document'
|
||||
>
|
||||
<ChipModalHeader showDivider={false}>Delete Document</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
title='Delete Document'
|
||||
description={
|
||||
<>
|
||||
Are you sure you want to delete{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>{effectiveDocumentName}</span>?{' '}
|
||||
<span className='text-[var(--text-error)]'>
|
||||
@@ -1224,27 +1200,15 @@ export function Document({
|
||||
) : (
|
||||
<>This action cannot be undone.</>
|
||||
)}
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip
|
||||
variant='filled'
|
||||
flush
|
||||
onClick={() => setShowDeleteDocumentDialog(false)}
|
||||
disabled={isDeletingDocument}
|
||||
>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='destructive'
|
||||
flush
|
||||
onClick={handleDeleteDocument}
|
||||
disabled={isDeletingDocument}
|
||||
>
|
||||
{isDeletingDocument ? 'Deleting...' : 'Delete Document'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
</>
|
||||
}
|
||||
confirm={{
|
||||
label: 'Delete Document',
|
||||
onClick: handleDeleteDocument,
|
||||
pending: isDeletingDocument,
|
||||
pendingLabel: 'Deleting...',
|
||||
}}
|
||||
/>
|
||||
|
||||
<ChunkContextMenu
|
||||
isOpen={isContextMenuOpen}
|
||||
|
||||
@@ -11,14 +11,13 @@ import { usePostHog } from 'posthog-js/react'
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
Chip,
|
||||
ChipConfirmModal,
|
||||
ChipDatePicker,
|
||||
ChipDropdown,
|
||||
type ChipDropdownOption,
|
||||
ChipInput,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalFooter,
|
||||
ChipModalHeader,
|
||||
chipVariants,
|
||||
Loader,
|
||||
@@ -1192,14 +1191,13 @@ export function KnowledgeBase({
|
||||
|
||||
<BaseTagsModal open={showTagsModal} onOpenChange={setShowTagsModal} knowledgeBaseId={id} />
|
||||
|
||||
<ChipModal
|
||||
<ChipConfirmModal
|
||||
open={showDeleteDialog}
|
||||
onOpenChange={setShowDeleteDialog}
|
||||
srTitle='Delete Knowledge Base'
|
||||
>
|
||||
<ChipModalHeader showDivider={false}>Delete Knowledge Base</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
title='Delete Knowledge Base'
|
||||
description={
|
||||
<>
|
||||
Are you sure you want to delete{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>{knowledgeBaseName}</span>?
|
||||
<span className='text-[var(--text-error)]'>
|
||||
@@ -1207,86 +1205,62 @@ export function KnowledgeBase({
|
||||
{pagination.total === 1 ? '' : 's'} within it will be removed.
|
||||
</span>{' '}
|
||||
You can restore it from Recently Deleted in Settings.
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip
|
||||
variant='filled'
|
||||
flush
|
||||
onClick={() => setShowDeleteDialog(false)}
|
||||
disabled={isDeleting}
|
||||
>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='destructive'
|
||||
flush
|
||||
onClick={handleDeleteKnowledgeBase}
|
||||
disabled={isDeleting}
|
||||
>
|
||||
{isDeleting ? 'Deleting...' : 'Delete Knowledge Base'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
</>
|
||||
}
|
||||
confirm={{
|
||||
label: 'Delete Knowledge Base',
|
||||
onClick: handleDeleteKnowledgeBase,
|
||||
pending: isDeleting,
|
||||
pendingLabel: 'Deleting...',
|
||||
}}
|
||||
/>
|
||||
|
||||
<ChipModal
|
||||
<ChipConfirmModal
|
||||
open={showDeleteDocumentModal}
|
||||
onOpenChange={setShowDeleteDocumentModal}
|
||||
onOpenChange={(open) => {
|
||||
setShowDeleteDocumentModal(open)
|
||||
if (!open) setDocumentToDelete(null)
|
||||
}}
|
||||
srTitle='Delete Document'
|
||||
>
|
||||
<ChipModalHeader showDivider={false}>Delete Document</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
{(() => {
|
||||
const docToDelete = documents.find((doc) => doc.id === documentToDelete)
|
||||
return (
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
Are you sure you want to delete{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>
|
||||
{docToDelete?.filename ?? 'this document'}
|
||||
title='Delete Document'
|
||||
description={(() => {
|
||||
const docToDelete = documents.find((doc) => doc.id === documentToDelete)
|
||||
return (
|
||||
<>
|
||||
Are you sure you want to delete{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>
|
||||
{docToDelete?.filename ?? 'this document'}
|
||||
</span>
|
||||
?{' '}
|
||||
{docToDelete?.connectorId ? (
|
||||
<span className='text-[var(--text-error)]'>
|
||||
This document is synced from a connector. Deleting it will permanently exclude it
|
||||
from future syncs. To temporarily hide it from search, disable it instead.
|
||||
</span>
|
||||
?{' '}
|
||||
{docToDelete?.connectorId ? (
|
||||
) : (
|
||||
<>
|
||||
<span className='text-[var(--text-error)]'>
|
||||
This document is synced from a connector. Deleting it will permanently exclude
|
||||
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>{' '}
|
||||
This action cannot be undone.
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
)
|
||||
})()}
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip
|
||||
variant='filled'
|
||||
flush
|
||||
onClick={() => {
|
||||
setShowDeleteDocumentModal(false)
|
||||
setDocumentToDelete(null)
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip variant='destructive' flush onClick={confirmDeleteDocument}>
|
||||
Delete Document
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
This will permanently delete the document.
|
||||
</span>{' '}
|
||||
This action cannot be undone.
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
})()}
|
||||
confirm={{
|
||||
label: 'Delete Document',
|
||||
onClick: confirmDeleteDocument,
|
||||
}}
|
||||
/>
|
||||
|
||||
<ChipModal
|
||||
<ChipConfirmModal
|
||||
open={showBulkDeleteModal}
|
||||
onOpenChange={setShowBulkDeleteModal}
|
||||
srTitle='Delete Documents'
|
||||
>
|
||||
<ChipModalHeader showDivider={false}>Delete Documents</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
title='Delete Documents'
|
||||
description={
|
||||
<>
|
||||
Are you sure you want to delete {selectedDocuments.size} document
|
||||
{selectedDocuments.size === 1 ? '' : 's'}?{' '}
|
||||
<span className='text-[var(--text-error)]'>
|
||||
@@ -1294,19 +1268,15 @@ export function KnowledgeBase({
|
||||
{selectedDocuments.size === 1 ? '' : 's'}.
|
||||
</span>{' '}
|
||||
This action cannot be undone.
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={() => setShowBulkDeleteModal(false)}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip variant='destructive' flush onClick={confirmBulkDelete} disabled={isBulkOperating}>
|
||||
{isBulkOperating
|
||||
? 'Deleting...'
|
||||
: `Delete ${selectedDocuments.size} Document${selectedDocuments.size === 1 ? '' : 's'}`}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
</>
|
||||
}
|
||||
confirm={{
|
||||
label: `Delete ${selectedDocuments.size} Document${selectedDocuments.size === 1 ? '' : 's'}`,
|
||||
onClick: confirmBulkDelete,
|
||||
pending: isBulkOperating,
|
||||
pendingLabel: 'Deleting...',
|
||||
}}
|
||||
/>
|
||||
|
||||
<AddDocumentsModal
|
||||
open={showAddDocumentsModal}
|
||||
|
||||
+9
-14
@@ -9,7 +9,6 @@ import {
|
||||
ButtonGroup,
|
||||
ButtonGroupItem,
|
||||
Checkbox,
|
||||
Chip,
|
||||
ChipCombobox,
|
||||
ChipInput,
|
||||
ChipModal,
|
||||
@@ -498,19 +497,15 @@ export function AddConnectorModal({
|
||||
</ChipModalBody>
|
||||
|
||||
{step === 'configure' && (
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={() => onOpenChange(false)} disabled={isCreating}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='primary'
|
||||
flush
|
||||
onClick={handleSubmit}
|
||||
disabled={!canSubmit || isCreating}
|
||||
>
|
||||
{isCreating ? 'Connecting…' : 'Connect & Sync'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
<ChipModalFooter
|
||||
onCancel={() => onOpenChange(false)}
|
||||
cancelDisabled={isCreating}
|
||||
primaryAction={{
|
||||
label: isCreating ? 'Connecting…' : 'Connect & Sync',
|
||||
onClick: handleSubmit,
|
||||
disabled: !canSubmit || isCreating,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</ChipModal>
|
||||
{showOAuthModal &&
|
||||
|
||||
+10
-15
@@ -6,7 +6,6 @@ import { RotateCcw, X } from 'lucide-react'
|
||||
import { useParams } from 'next/navigation'
|
||||
import {
|
||||
Button,
|
||||
Chip,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalError,
|
||||
@@ -258,25 +257,21 @@ export function AddDocumentsModal({
|
||||
{uploadError && <ChipModalError>{uploadError.message}</ChipModalError>}
|
||||
</ChipModalBody>
|
||||
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={handleClose} disabled={isUploading}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='primary'
|
||||
flush
|
||||
onClick={handleUpload}
|
||||
disabled={files.length === 0 || isUploading}
|
||||
>
|
||||
{isUploading
|
||||
<ChipModalFooter
|
||||
onCancel={handleClose}
|
||||
cancelDisabled={isUploading}
|
||||
primaryAction={{
|
||||
label: isUploading
|
||||
? uploadProgress.stage === 'uploading'
|
||||
? `Uploading ${uploadProgress.filesCompleted}/${uploadProgress.totalFiles}...`
|
||||
: uploadProgress.stage === 'processing'
|
||||
? 'Processing...'
|
||||
: 'Uploading...'
|
||||
: 'Upload'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
: 'Upload',
|
||||
onClick: handleUpload,
|
||||
disabled: files.length === 0 || isUploading,
|
||||
}}
|
||||
/>
|
||||
</ChipModal>
|
||||
)
|
||||
}
|
||||
|
||||
+36
-47
@@ -4,8 +4,8 @@ import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import {
|
||||
Button,
|
||||
Chip,
|
||||
ChipCombobox,
|
||||
ChipConfirmModal,
|
||||
ChipInput,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
@@ -397,22 +397,23 @@ export function BaseTagsModal({ open, onOpenChange, knowledgeBaseId }: BaseTagsM
|
||||
</div>
|
||||
</ChipModalBody>
|
||||
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={() => handleClose(false)}>
|
||||
Close
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
<ChipModalFooter
|
||||
onCancel={() => handleClose(false)}
|
||||
primaryAction={{ label: 'Close', onClick: () => handleClose(false) }}
|
||||
/>
|
||||
</ChipModal>
|
||||
|
||||
{/* Delete Tag Confirmation Dialog */}
|
||||
<ChipModal
|
||||
<ChipConfirmModal
|
||||
open={deleteTagDialogOpen}
|
||||
onOpenChange={setDeleteTagDialogOpen}
|
||||
onOpenChange={(open) => {
|
||||
setDeleteTagDialogOpen(open)
|
||||
if (!open) setSelectedTag(null)
|
||||
}}
|
||||
srTitle='Delete Tag'
|
||||
>
|
||||
<ChipModalHeader showDivider={false}>Delete Tag</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
title='Delete Tag'
|
||||
description={
|
||||
<>
|
||||
Are you sure you want to delete the “
|
||||
<span className='text-[var(--text-primary)]'>{selectedTag?.displayName}</span>”
|
||||
tag?{' '}
|
||||
@@ -421,36 +422,25 @@ export function BaseTagsModal({ open, onOpenChange, knowledgeBaseId }: BaseTagsM
|
||||
{selectedTagUsage?.documentCount !== 1 ? 's' : ''}.
|
||||
</span>{' '}
|
||||
This action cannot be undone.
|
||||
</p>
|
||||
{selectedTagUsage && selectedTagUsage.documentCount > 0 && (
|
||||
<div className='flex flex-col gap-2 px-2'>
|
||||
<Label>Affected documents:</Label>
|
||||
<DocumentList
|
||||
documents={selectedTagUsage.documents}
|
||||
totalCount={selectedTagUsage.documentCount}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip
|
||||
variant='filled'
|
||||
flush
|
||||
disabled={deleteTagMutation.isPending}
|
||||
onClick={() => setDeleteTagDialogOpen(false)}
|
||||
>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='destructive'
|
||||
flush
|
||||
onClick={confirmDeleteTag}
|
||||
disabled={deleteTagMutation.isPending}
|
||||
>
|
||||
{deleteTagMutation.isPending ? 'Deleting...' : 'Delete Tag'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
</>
|
||||
}
|
||||
confirm={{
|
||||
label: 'Delete Tag',
|
||||
onClick: confirmDeleteTag,
|
||||
pending: deleteTagMutation.isPending,
|
||||
pendingLabel: 'Deleting...',
|
||||
}}
|
||||
>
|
||||
{selectedTagUsage && selectedTagUsage.documentCount > 0 && (
|
||||
<div className='flex flex-col gap-2 px-2'>
|
||||
<Label>Affected documents:</Label>
|
||||
<DocumentList
|
||||
documents={selectedTagUsage.documents}
|
||||
totalCount={selectedTagUsage.documentCount}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</ChipConfirmModal>
|
||||
|
||||
{/* View Documents Dialog */}
|
||||
<ChipModal
|
||||
@@ -485,11 +475,10 @@ export function BaseTagsModal({ open, onOpenChange, knowledgeBaseId }: BaseTagsM
|
||||
)}
|
||||
</div>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={() => setViewDocumentsDialogOpen(false)}>
|
||||
Close
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
<ChipModalFooter
|
||||
onCancel={() => setViewDocumentsDialogOpen(false)}
|
||||
primaryAction={{ label: 'Close', onClick: () => setViewDocumentsDialogOpen(false) }}
|
||||
/>
|
||||
</ChipModal>
|
||||
</>
|
||||
)
|
||||
|
||||
+44
-57
@@ -19,11 +19,7 @@ import {
|
||||
Badge,
|
||||
Button,
|
||||
Checkbox,
|
||||
Chip,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalFooter,
|
||||
ChipModalHeader,
|
||||
ChipConfirmModal,
|
||||
Loader,
|
||||
Skeleton,
|
||||
Tooltip,
|
||||
@@ -183,6 +179,24 @@ export function ConnectorsSection({
|
||||
[knowledgeBaseId, updateConnector, addToSet, removeFromSet]
|
||||
)
|
||||
|
||||
const handleDeleteConnector = () => {
|
||||
if (!deleteTarget) return
|
||||
deleteConnector(
|
||||
{ knowledgeBaseId, connectorId: deleteTarget, deleteDocuments },
|
||||
{
|
||||
onSuccess: () => {
|
||||
setError(null)
|
||||
closeDeleteModal()
|
||||
},
|
||||
onError: (err) => {
|
||||
logger.error('Delete connector failed', { error: err.message })
|
||||
setError(err.message)
|
||||
closeDeleteModal()
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (connectors.length === 0 && !canEdit && !isLoading) return null
|
||||
|
||||
return (
|
||||
@@ -240,62 +254,35 @@ export function ConnectorsSection({
|
||||
/>
|
||||
)}
|
||||
|
||||
<ChipModal
|
||||
<ChipConfirmModal
|
||||
open={deleteTarget !== null}
|
||||
onOpenChange={closeDeleteModal}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) closeDeleteModal()
|
||||
}}
|
||||
srTitle='Remove Connector'
|
||||
title='Remove Connector'
|
||||
description='This will disconnect the source and stop future syncs. Documents already synced will remain in the knowledge base unless you choose to delete them.'
|
||||
confirm={{
|
||||
label: 'Remove',
|
||||
onClick: handleDeleteConnector,
|
||||
pending: isDeleting,
|
||||
pendingLabel: 'Removing...',
|
||||
}}
|
||||
>
|
||||
<ChipModalHeader showDivider={false}>Remove Connector</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
This will disconnect the source and stop future syncs. Documents already synced will
|
||||
remain in the knowledge base unless you choose to delete them.
|
||||
</p>
|
||||
<div className='flex items-center gap-2 px-2'>
|
||||
<Checkbox
|
||||
id={deleteDocumentsId}
|
||||
checked={deleteDocuments}
|
||||
onCheckedChange={(checked) => setDeleteDocuments(checked === true)}
|
||||
/>
|
||||
<label
|
||||
htmlFor={deleteDocumentsId}
|
||||
className='cursor-pointer text-[var(--text-secondary)] text-small'
|
||||
>
|
||||
Also delete all synced documents
|
||||
</label>
|
||||
</div>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={closeDeleteModal} disabled={isDeleting}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='destructive'
|
||||
flush
|
||||
disabled={isDeleting}
|
||||
onClick={() => {
|
||||
if (deleteTarget) {
|
||||
deleteConnector(
|
||||
{ knowledgeBaseId, connectorId: deleteTarget, deleteDocuments },
|
||||
{
|
||||
onSuccess: () => {
|
||||
setError(null)
|
||||
closeDeleteModal()
|
||||
},
|
||||
onError: (err) => {
|
||||
logger.error('Delete connector failed', { error: err.message })
|
||||
setError(err.message)
|
||||
closeDeleteModal()
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
}}
|
||||
<div className='flex items-center gap-2 px-2'>
|
||||
<Checkbox
|
||||
id={deleteDocumentsId}
|
||||
checked={deleteDocuments}
|
||||
onCheckedChange={(checked) => setDeleteDocuments(checked === true)}
|
||||
/>
|
||||
<label
|
||||
htmlFor={deleteDocumentsId}
|
||||
className='cursor-pointer text-[var(--text-secondary)] text-small'
|
||||
>
|
||||
{isDeleting ? 'Removing...' : 'Remove'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
Also delete all synced documents
|
||||
</label>
|
||||
</div>
|
||||
</ChipConfirmModal>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
+9
-9
@@ -7,7 +7,6 @@ import {
|
||||
Button,
|
||||
ButtonGroup,
|
||||
ButtonGroupItem,
|
||||
Chip,
|
||||
ChipCombobox,
|
||||
ChipInput,
|
||||
ChipModal,
|
||||
@@ -314,14 +313,15 @@ export function EditConnectorModal({
|
||||
</ChipModalBody>
|
||||
|
||||
{activeTab === 'settings' && (
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={() => onOpenChange(false)} disabled={isSaving}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip variant='primary' flush onClick={handleSave} disabled={!hasChanges || isSaving}>
|
||||
{isSaving ? 'Saving…' : 'Save'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
<ChipModalFooter
|
||||
onCancel={() => onOpenChange(false)}
|
||||
cancelDisabled={isSaving}
|
||||
primaryAction={{
|
||||
label: isSaving ? 'Saving…' : 'Save',
|
||||
onClick: handleSave,
|
||||
disabled: !hasChanges || isSaving,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</ChipModal>
|
||||
)
|
||||
|
||||
+9
-14
@@ -4,7 +4,6 @@ import { useRef, useState } from 'react'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { getErrorMessage } from '@sim/utils/errors'
|
||||
import {
|
||||
Chip,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalError,
|
||||
@@ -102,19 +101,15 @@ export function RenameDocumentModal({
|
||||
/>
|
||||
<ChipModalError>{error}</ChipModalError>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={() => onOpenChange(false)} disabled={isSubmitting}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='primary'
|
||||
flush
|
||||
onClick={() => void handleSubmit()}
|
||||
disabled={isSubmitting || !name?.trim() || name.trim() === initialName}
|
||||
>
|
||||
{isSubmitting ? 'Renaming...' : 'Rename'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
<ChipModalFooter
|
||||
onCancel={() => onOpenChange(false)}
|
||||
cancelDisabled={isSubmitting}
|
||||
primaryAction={{
|
||||
label: isSubmitting ? 'Renaming...' : 'Rename',
|
||||
onClick: () => void handleSubmit(),
|
||||
disabled: isSubmitting || !name?.trim() || name.trim() === initialName,
|
||||
}}
|
||||
/>
|
||||
</ChipModal>
|
||||
)
|
||||
}
|
||||
|
||||
+11
-16
@@ -11,7 +11,6 @@ import { z } from 'zod'
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
Chip,
|
||||
ChipCombobox,
|
||||
ChipInput,
|
||||
ChipModal,
|
||||
@@ -321,6 +320,7 @@ export const CreateBaseModal = memo(function CreateBaseModal({
|
||||
<ChipModalHeader onClose={() => handleClose(false)}>Create Knowledge Base</ChipModalHeader>
|
||||
|
||||
<form onSubmit={handleSubmit(onSubmit)} className='flex min-h-0 flex-1 flex-col'>
|
||||
<button type='submit' hidden disabled={isSubmitting || !nameValue?.trim()} />
|
||||
<ChipModalBody className='max-h-[70vh] overflow-y-auto'>
|
||||
<div ref={scrollContainerRef} className='min-h-0 flex-1'>
|
||||
<div className='space-y-3'>
|
||||
@@ -578,18 +578,11 @@ export const CreateBaseModal = memo(function CreateBaseModal({
|
||||
<ChipModalError>{uploadError?.message || submitStatus?.message}</ChipModalError>
|
||||
</ChipModalBody>
|
||||
|
||||
<ChipModalFooter>
|
||||
<Chip
|
||||
variant='filled'
|
||||
flush
|
||||
onClick={() => handleClose(false)}
|
||||
type='button'
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip variant='primary' flush type='submit' disabled={isSubmitting || !nameValue?.trim()}>
|
||||
{isSubmitting
|
||||
<ChipModalFooter
|
||||
onCancel={() => handleClose(false)}
|
||||
cancelDisabled={isSubmitting}
|
||||
primaryAction={{
|
||||
label: isSubmitting
|
||||
? isUploading
|
||||
? uploadProgress.stage === 'uploading'
|
||||
? `Uploading ${uploadProgress.filesCompleted}/${uploadProgress.totalFiles}...`
|
||||
@@ -597,9 +590,11 @@ export const CreateBaseModal = memo(function CreateBaseModal({
|
||||
? 'Processing...'
|
||||
: 'Creating...'
|
||||
: 'Creating...'
|
||||
: 'Create'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
: 'Create',
|
||||
onClick: handleSubmit(onSubmit),
|
||||
disabled: isSubmitting || !nameValue?.trim(),
|
||||
}}
|
||||
/>
|
||||
</form>
|
||||
</ChipModal>
|
||||
)
|
||||
|
||||
+17
-18
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { memo } from 'react'
|
||||
import { Chip, ChipModal, ChipModalBody, ChipModalFooter, ChipModalHeader } from '@/components/emcn'
|
||||
import { ChipConfirmModal } from '@/components/emcn'
|
||||
|
||||
interface DeleteKnowledgeBaseModalProps {
|
||||
/**
|
||||
@@ -38,12 +38,13 @@ export const DeleteKnowledgeBaseModal = memo(function DeleteKnowledgeBaseModal({
|
||||
knowledgeBaseName,
|
||||
}: DeleteKnowledgeBaseModalProps) {
|
||||
return (
|
||||
<ChipModal open={isOpen} onOpenChange={onClose} srTitle='Delete Knowledge Base'>
|
||||
<ChipModalHeader onClose={onClose} showDivider={false}>
|
||||
Delete Knowledge Base
|
||||
</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
<ChipConfirmModal
|
||||
open={isOpen}
|
||||
onOpenChange={onClose}
|
||||
srTitle='Delete Knowledge Base'
|
||||
title='Delete Knowledge Base'
|
||||
description={
|
||||
<>
|
||||
{knowledgeBaseName ? (
|
||||
<>
|
||||
Are you sure you want to delete{' '}
|
||||
@@ -61,16 +62,14 @@ export const DeleteKnowledgeBaseModal = memo(function DeleteKnowledgeBaseModal({
|
||||
</>
|
||||
)}{' '}
|
||||
You can restore it from Recently Deleted in Settings.
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={onClose} disabled={isDeleting}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip variant='destructive' flush onClick={onConfirm} disabled={isDeleting}>
|
||||
{isDeleting ? 'Deleting...' : 'Delete'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
</>
|
||||
}
|
||||
confirm={{
|
||||
label: 'Delete',
|
||||
onClick: onConfirm,
|
||||
pending: isDeleting,
|
||||
pendingLabel: 'Deleting...',
|
||||
}}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
+9
-14
@@ -4,7 +4,6 @@ import { memo, useEffect, useState } from 'react'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { getErrorMessage } from '@sim/utils/errors'
|
||||
import {
|
||||
Chip,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalError,
|
||||
@@ -156,19 +155,15 @@ export const EditKnowledgeBaseModal = memo(function EditKnowledgeBaseModal({
|
||||
)}
|
||||
<ChipModalError>{error}</ChipModalError>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={() => onOpenChange(false)} disabled={isSubmitting}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='primary'
|
||||
flush
|
||||
onClick={handleSubmit}
|
||||
disabled={!isValid || !isDirty || isSubmitting}
|
||||
>
|
||||
{isSubmitting ? 'Saving...' : 'Save'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
<ChipModalFooter
|
||||
onCancel={() => onOpenChange(false)}
|
||||
cancelDisabled={isSubmitting}
|
||||
primaryAction={{
|
||||
label: isSubmitting ? 'Saving...' : 'Save',
|
||||
onClick: handleSubmit,
|
||||
disabled: !isValid || !isDirty || isSubmitting,
|
||||
}}
|
||||
/>
|
||||
</ChipModal>
|
||||
)
|
||||
})
|
||||
|
||||
+55
-73
@@ -4,12 +4,12 @@ import type { ReactNode } from 'react'
|
||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { getErrorMessage } from '@sim/utils/errors'
|
||||
import { Plus, X } from 'lucide-react'
|
||||
import { X } from 'lucide-react'
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
Chip,
|
||||
ChipCombobox,
|
||||
ChipConfirmModal,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalFooter,
|
||||
@@ -508,6 +508,16 @@ export const NotificationSettings = memo(function NotificationSettings({
|
||||
}
|
||||
}
|
||||
|
||||
const handleBackToList = () => {
|
||||
resetForm()
|
||||
setShowForm(false)
|
||||
}
|
||||
|
||||
const handleAddNew = () => {
|
||||
resetForm()
|
||||
setShowForm(true)
|
||||
}
|
||||
|
||||
const handleEdit = (subscription: NotificationSubscription) => {
|
||||
setActiveTab(subscription.notificationType)
|
||||
setEditingId(subscription.id)
|
||||
@@ -1229,86 +1239,58 @@ export const NotificationSettings = memo(function NotificationSettings({
|
||||
</ChipModalBody>
|
||||
|
||||
<ChipModalFooter
|
||||
leading={
|
||||
displayForm && hasSubscriptions ? (
|
||||
<Chip
|
||||
variant='filled'
|
||||
flush
|
||||
onClick={() => {
|
||||
resetForm()
|
||||
setShowForm(false)
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</Chip>
|
||||
) : undefined
|
||||
onCancel={handleClose}
|
||||
secondaryAction={
|
||||
displayForm && hasSubscriptions
|
||||
? { label: 'Back', onClick: handleBackToList }
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{displayForm ? (
|
||||
<Chip
|
||||
variant='primary'
|
||||
flush
|
||||
onClick={handleSave}
|
||||
disabled={createNotification.isPending || updateNotification.isPending}
|
||||
>
|
||||
{createNotification.isPending || updateNotification.isPending
|
||||
? editingId
|
||||
? 'Updating...'
|
||||
: 'Creating...'
|
||||
: editingId
|
||||
? 'Update'
|
||||
: 'Create'}
|
||||
</Chip>
|
||||
) : (
|
||||
<Chip
|
||||
variant='primary'
|
||||
flush
|
||||
leftIcon={Plus}
|
||||
onClick={() => {
|
||||
resetForm()
|
||||
setShowForm(true)
|
||||
}}
|
||||
disabled={isLoading}
|
||||
>
|
||||
Add
|
||||
</Chip>
|
||||
)}
|
||||
</ChipModalFooter>
|
||||
primaryAction={
|
||||
displayForm
|
||||
? {
|
||||
label:
|
||||
createNotification.isPending || updateNotification.isPending
|
||||
? editingId
|
||||
? 'Updating...'
|
||||
: 'Creating...'
|
||||
: editingId
|
||||
? 'Update'
|
||||
: 'Create',
|
||||
onClick: handleSave,
|
||||
disabled: createNotification.isPending || updateNotification.isPending,
|
||||
}
|
||||
: {
|
||||
label: 'Add',
|
||||
onClick: handleAddNew,
|
||||
disabled: isLoading,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</ChipModal>
|
||||
|
||||
<ChipModal
|
||||
<ChipConfirmModal
|
||||
open={showDeleteDialog}
|
||||
onOpenChange={setShowDeleteDialog}
|
||||
onOpenChange={(next) => {
|
||||
if (!next) setDeletingId(null)
|
||||
setShowDeleteDialog(next)
|
||||
}}
|
||||
srTitle='Delete Notification'
|
||||
>
|
||||
<ChipModalHeader showDivider={false}>Delete Notification</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
title='Delete Notification'
|
||||
description={
|
||||
<>
|
||||
<span className='text-[var(--text-error)]'>
|
||||
This will permanently remove the notification and stop all deliveries.
|
||||
</span>{' '}
|
||||
This action cannot be undone.
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip
|
||||
variant='filled'
|
||||
flush
|
||||
disabled={deleteNotification.isPending}
|
||||
onClick={() => setShowDeleteDialog(false)}
|
||||
>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='destructive'
|
||||
flush
|
||||
onClick={handleDelete}
|
||||
disabled={deleteNotification.isPending}
|
||||
>
|
||||
{deleteNotification.isPending ? 'Deleting...' : 'Delete'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
</>
|
||||
}
|
||||
confirm={{
|
||||
label: 'Delete',
|
||||
onClick: handleDelete,
|
||||
pending: deleteNotification.isPending,
|
||||
pendingLabel: 'Deleting...',
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
+10
-17
@@ -6,7 +6,6 @@ import { getErrorMessage } from '@sim/utils/errors'
|
||||
import {
|
||||
ButtonGroup,
|
||||
ButtonGroupItem,
|
||||
Chip,
|
||||
ChipCombobox,
|
||||
ChipInput,
|
||||
ChipModal,
|
||||
@@ -511,27 +510,21 @@ export function ScheduleModal({ open, onOpenChange, workspaceId, schedule }: Sch
|
||||
</div>
|
||||
</ChipModalBody>
|
||||
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={handleClose}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='primary'
|
||||
flush
|
||||
onClick={handleSubmit}
|
||||
disabled={
|
||||
!isFormValid || createScheduleMutation.isPending || updateScheduleMutation.isPending
|
||||
}
|
||||
>
|
||||
{isEditing
|
||||
<ChipModalFooter
|
||||
onCancel={handleClose}
|
||||
primaryAction={{
|
||||
label: isEditing
|
||||
? updateScheduleMutation.isPending
|
||||
? 'Saving...'
|
||||
: 'Save changes'
|
||||
: createScheduleMutation.isPending
|
||||
? 'Creating...'
|
||||
: 'Create'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
: 'Create',
|
||||
onClick: handleSubmit,
|
||||
disabled:
|
||||
!isFormValid || createScheduleMutation.isPending || updateScheduleMutation.isPending,
|
||||
}}
|
||||
/>
|
||||
</ChipModal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,14 +4,7 @@ import { useCallback, useMemo, useState } from 'react'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { formatAbsoluteDate } from '@sim/utils/formatting'
|
||||
import { useParams } from 'next/navigation'
|
||||
import {
|
||||
Chip,
|
||||
ChipCombobox,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalFooter,
|
||||
ChipModalHeader,
|
||||
} from '@/components/emcn'
|
||||
import { ChipCombobox, ChipConfirmModal } from '@/components/emcn'
|
||||
import { Calendar } from '@/components/emcn/icons'
|
||||
import { parseCronToHumanReadable } from '@/lib/workflows/schedules/utils'
|
||||
import type {
|
||||
@@ -219,6 +212,11 @@ export function ScheduledTasks() {
|
||||
}
|
||||
}
|
||||
|
||||
const handleDeleteDialogOpenChange = (open: boolean) => {
|
||||
setIsDeleteDialogOpen(open)
|
||||
if (!open) setActiveTask(null)
|
||||
}
|
||||
|
||||
const handlePause = async () => {
|
||||
if (!activeTask) return
|
||||
try {
|
||||
@@ -443,43 +441,27 @@ export function ScheduledTasks() {
|
||||
schedule={activeTask ?? undefined}
|
||||
/>
|
||||
|
||||
<ChipModal
|
||||
<ChipConfirmModal
|
||||
open={isDeleteDialogOpen}
|
||||
onOpenChange={setIsDeleteDialogOpen}
|
||||
onOpenChange={handleDeleteDialogOpenChange}
|
||||
srTitle='Delete Scheduled Task'
|
||||
>
|
||||
<ChipModalHeader showDivider={false}>Delete Scheduled Task</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
title='Delete Scheduled Task'
|
||||
description={
|
||||
<>
|
||||
Are you sure you want to delete{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>
|
||||
{activeTask?.jobTitle || 'this task'}
|
||||
</span>
|
||||
? This action cannot be undone.
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip
|
||||
variant='filled'
|
||||
flush
|
||||
onClick={() => {
|
||||
setIsDeleteDialogOpen(false)
|
||||
setActiveTask(null)
|
||||
}}
|
||||
disabled={deleteSchedule.isPending}
|
||||
>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='destructive'
|
||||
flush
|
||||
onClick={handleDelete}
|
||||
disabled={deleteSchedule.isPending}
|
||||
>
|
||||
{deleteSchedule.isPending ? 'Deleting...' : 'Delete'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
</>
|
||||
}
|
||||
confirm={{
|
||||
label: 'Delete',
|
||||
onClick: handleDelete,
|
||||
pending: deleteSchedule.isPending,
|
||||
pendingLabel: 'Deleting...',
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,17 +5,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { formatDate } from '@sim/utils/formatting'
|
||||
import { Info, Plus } from 'lucide-react'
|
||||
import { useParams } from 'next/navigation'
|
||||
import {
|
||||
Chip,
|
||||
ChipInput,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalFooter,
|
||||
ChipModalHeader,
|
||||
Search,
|
||||
Switch,
|
||||
Tooltip,
|
||||
} from '@/components/emcn'
|
||||
import { Chip, ChipConfirmModal, ChipInput, Search, Switch, Tooltip } from '@/components/emcn'
|
||||
import { useSession } from '@/lib/auth/auth-client'
|
||||
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
|
||||
import { SettingsSection } from '@/app/workspace/[workspaceId]/settings/components/settings-section/settings-section'
|
||||
@@ -339,44 +329,33 @@ export function ApiKeys() {
|
||||
/>
|
||||
|
||||
{/* Delete Confirmation Dialog */}
|
||||
<ChipModal
|
||||
<ChipConfirmModal
|
||||
open={showDeleteDialog}
|
||||
onOpenChange={setShowDeleteDialog}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
setShowDeleteDialog(false)
|
||||
setDeleteKey(null)
|
||||
}
|
||||
}}
|
||||
srTitle='Delete API key'
|
||||
>
|
||||
<ChipModalHeader showDivider={false}>Delete API key</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
title='Delete API key'
|
||||
description={
|
||||
<>
|
||||
Deleting{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>{deleteKey?.name}</span>{' '}
|
||||
<span className='text-[var(--text-error)]'>
|
||||
will immediately revoke access for any integrations using it.
|
||||
</span>{' '}
|
||||
This action cannot be undone.
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip
|
||||
variant='filled'
|
||||
flush
|
||||
onClick={() => {
|
||||
setShowDeleteDialog(false)
|
||||
setDeleteKey(null)
|
||||
}}
|
||||
disabled={deleteApiKeyMutation.isPending}
|
||||
>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='destructive'
|
||||
flush
|
||||
onClick={handleDeleteKey}
|
||||
disabled={deleteApiKeyMutation.isPending}
|
||||
>
|
||||
{deleteApiKeyMutation.isPending ? 'Deleting...' : 'Delete'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
</>
|
||||
}
|
||||
confirm={{
|
||||
label: 'Delete',
|
||||
onClick: handleDeleteKey,
|
||||
pending: deleteApiKeyMutation.isPending,
|
||||
pendingLabel: 'Deleting...',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
+13
-21
@@ -6,7 +6,6 @@ import { getErrorMessage } from '@sim/utils/errors'
|
||||
import {
|
||||
ButtonGroup,
|
||||
ButtonGroupItem,
|
||||
Chip,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalError,
|
||||
@@ -155,23 +154,17 @@ export function CreateApiKeyModal({
|
||||
/>
|
||||
<ChipModalError>{createError}</ChipModalError>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={handleClose}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='primary'
|
||||
flush
|
||||
onClick={handleCreateKey}
|
||||
disabled={
|
||||
<ChipModalFooter
|
||||
onCancel={handleClose}
|
||||
primaryAction={{
|
||||
label: createApiKeyMutation.isPending ? 'Creating...' : 'Create',
|
||||
onClick: handleCreateKey,
|
||||
disabled:
|
||||
!keyName.trim() ||
|
||||
createApiKeyMutation.isPending ||
|
||||
(keyType === 'workspace' && !canManageWorkspaceKeys)
|
||||
}
|
||||
>
|
||||
{createApiKeyMutation.isPending ? 'Creating...' : 'Create'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
(keyType === 'workspace' && !canManageWorkspaceKeys),
|
||||
}}
|
||||
/>
|
||||
</ChipModal>
|
||||
|
||||
{/* New API Key Dialog - shows the created key */}
|
||||
@@ -193,11 +186,10 @@ export function CreateApiKeyModal({
|
||||
{newKey && <SecretReveal value={newKey.key} />}
|
||||
</ChipModalField>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='primary' flush onClick={() => setShowNewKeyDialog(false)}>
|
||||
Done
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
<ChipModalFooter
|
||||
onCancel={() => setShowNewKeyDialog(false)}
|
||||
primaryAction={{ label: 'Done', onClick: () => setShowNewKeyDialog(false) }}
|
||||
/>
|
||||
</ChipModal>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useParams } from 'next/navigation'
|
||||
import {
|
||||
Button,
|
||||
Chip,
|
||||
ChipConfirmModal,
|
||||
ChipInput,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
@@ -527,39 +528,31 @@ export function BYOK() {
|
||||
</ChipModalField>
|
||||
<ChipModalError>{error}</ChipModalError>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip
|
||||
variant='filled'
|
||||
flush
|
||||
onClick={() => {
|
||||
setEditingProvider(null)
|
||||
setApiKeyInput('')
|
||||
setShowApiKey(false)
|
||||
setError(null)
|
||||
}}
|
||||
disabled={upsertKey.isPending}
|
||||
>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='primary'
|
||||
flush
|
||||
onClick={handleSave}
|
||||
disabled={!apiKeyInput.trim() || upsertKey.isPending}
|
||||
>
|
||||
{upsertKey.isPending ? 'Saving...' : 'Save'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
<ChipModalFooter
|
||||
onCancel={() => {
|
||||
setEditingProvider(null)
|
||||
setApiKeyInput('')
|
||||
setShowApiKey(false)
|
||||
setError(null)
|
||||
}}
|
||||
cancelDisabled={upsertKey.isPending}
|
||||
primaryAction={{
|
||||
label: upsertKey.isPending ? 'Saving...' : 'Save',
|
||||
onClick: handleSave,
|
||||
disabled: !apiKeyInput.trim() || upsertKey.isPending,
|
||||
}}
|
||||
/>
|
||||
</ChipModal>
|
||||
|
||||
<ChipModal
|
||||
<ChipConfirmModal
|
||||
open={!!deleteConfirmProvider}
|
||||
onOpenChange={() => setDeleteConfirmProvider(null)}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) setDeleteConfirmProvider(null)
|
||||
}}
|
||||
srTitle='Delete API Key'
|
||||
>
|
||||
<ChipModalHeader showDivider={false}>Delete API Key</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
title='Delete API Key'
|
||||
description={
|
||||
<>
|
||||
Are you sure you want to delete the{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>
|
||||
{PROVIDERS.find((p) => p.id === deleteConfirmProvider)?.name}
|
||||
@@ -569,17 +562,15 @@ export function BYOK() {
|
||||
This workspace will revert to using platform hosted keys.
|
||||
</span>{' '}
|
||||
This action cannot be undone.
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={() => setDeleteConfirmProvider(null)}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip variant='destructive' flush onClick={handleDelete} disabled={deleteKey.isPending}>
|
||||
{deleteKey.isPending ? 'Deleting...' : 'Delete'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
</>
|
||||
}
|
||||
confirm={{
|
||||
label: 'Delete',
|
||||
onClick: handleDelete,
|
||||
pending: deleteKey.isPending,
|
||||
pendingLabel: 'Deleting...',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { formatDate } from '@sim/utils/formatting'
|
||||
import { Plus } from 'lucide-react'
|
||||
import {
|
||||
Chip,
|
||||
ChipConfirmModal,
|
||||
ChipInput,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
@@ -275,27 +276,18 @@ export function Copilot() {
|
||||
/>
|
||||
<ChipModalError>{createError}</ChipModalError>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip
|
||||
variant='filled'
|
||||
flush
|
||||
onClick={() => {
|
||||
setIsCreateDialogOpen(false)
|
||||
setNewKeyName('')
|
||||
setCreateError(null)
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='primary'
|
||||
flush
|
||||
onClick={handleCreateKey}
|
||||
disabled={!newKeyName.trim() || generateKey.isPending}
|
||||
>
|
||||
{generateKey.isPending ? 'Creating...' : 'Create'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
<ChipModalFooter
|
||||
onCancel={() => {
|
||||
setIsCreateDialogOpen(false)
|
||||
setNewKeyName('')
|
||||
setCreateError(null)
|
||||
}}
|
||||
primaryAction={{
|
||||
label: generateKey.isPending ? 'Creating...' : 'Create',
|
||||
onClick: handleCreateKey,
|
||||
disabled: !newKeyName.trim() || generateKey.isPending,
|
||||
}}
|
||||
/>
|
||||
</ChipModal>
|
||||
|
||||
{/* New API Key Dialog */}
|
||||
@@ -322,29 +314,34 @@ export function Copilot() {
|
||||
{newKey && <SecretReveal value={newKey} />}
|
||||
</ChipModalField>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip
|
||||
variant='primary'
|
||||
flush
|
||||
onClick={() => {
|
||||
<ChipModalFooter
|
||||
onCancel={() => {
|
||||
setShowNewKeyDialog(false)
|
||||
setNewKey(null)
|
||||
}}
|
||||
primaryAction={{
|
||||
label: 'Done',
|
||||
onClick: () => {
|
||||
setShowNewKeyDialog(false)
|
||||
setNewKey(null)
|
||||
}}
|
||||
>
|
||||
Done
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</ChipModal>
|
||||
|
||||
{/* Delete Confirmation Dialog */}
|
||||
<ChipModal
|
||||
<ChipConfirmModal
|
||||
open={showDeleteDialog}
|
||||
onOpenChange={setShowDeleteDialog}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
setShowDeleteDialog(false)
|
||||
setDeleteKey(null)
|
||||
}
|
||||
}}
|
||||
srTitle='Delete API key'
|
||||
>
|
||||
<ChipModalHeader showDivider={false}>Delete API key</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
title='Delete API key'
|
||||
description={
|
||||
<>
|
||||
Deleting{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>
|
||||
{deleteKey?.name || 'Unnamed Key'}
|
||||
@@ -353,30 +350,15 @@ export function Copilot() {
|
||||
will immediately revoke access for any integrations using it.
|
||||
</span>{' '}
|
||||
This action cannot be undone.
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip
|
||||
variant='filled'
|
||||
flush
|
||||
onClick={() => {
|
||||
setShowDeleteDialog(false)
|
||||
setDeleteKey(null)
|
||||
}}
|
||||
disabled={deleteKeyMutation.isPending}
|
||||
>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='destructive'
|
||||
flush
|
||||
onClick={handleDeleteKey}
|
||||
disabled={deleteKeyMutation.isPending}
|
||||
>
|
||||
{deleteKeyMutation.isPending ? 'Deleting...' : 'Delete'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
</>
|
||||
}
|
||||
confirm={{
|
||||
label: 'Delete',
|
||||
onClick: handleDeleteKey,
|
||||
pending: deleteKeyMutation.isPending,
|
||||
pendingLabel: 'Deleting...',
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
+41
-57
@@ -11,6 +11,7 @@ import {
|
||||
ButtonGroup,
|
||||
ButtonGroupItem,
|
||||
Chip,
|
||||
ChipConfirmModal,
|
||||
ChipInput,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
@@ -781,78 +782,61 @@ export function CredentialSets() {
|
||||
</ChipModalField>
|
||||
<ChipModalError>{createError}</ChipModalError>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={handleCloseCreateModal}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='primary'
|
||||
flush
|
||||
onClick={handleCreateCredentialSet}
|
||||
disabled={!newSetName.trim() || createCredentialSet.isPending}
|
||||
>
|
||||
{createCredentialSet.isPending ? 'Creating...' : 'Create'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
<ChipModalFooter
|
||||
onCancel={handleCloseCreateModal}
|
||||
primaryAction={{
|
||||
label: createCredentialSet.isPending ? 'Creating...' : 'Create',
|
||||
onClick: handleCreateCredentialSet,
|
||||
disabled: !newSetName.trim() || createCredentialSet.isPending,
|
||||
}}
|
||||
/>
|
||||
</ChipModal>
|
||||
|
||||
<ChipModal
|
||||
<ChipConfirmModal
|
||||
open={!!leavingMembership}
|
||||
onOpenChange={() => setLeavingMembership(null)}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) setLeavingMembership(null)
|
||||
}}
|
||||
srTitle='Leave Polling Group'
|
||||
>
|
||||
<ChipModalHeader showDivider={false}>Leave Polling Group</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
title='Leave Polling Group'
|
||||
description={
|
||||
<>
|
||||
Are you sure you want to leave{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>
|
||||
{leavingMembership?.name}
|
||||
</span>
|
||||
? Your email account will no longer be polled in workflows using this group.
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={() => setLeavingMembership(null)}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='destructive'
|
||||
flush
|
||||
onClick={confirmLeave}
|
||||
disabled={leaveCredentialSet.isPending}
|
||||
>
|
||||
{leaveCredentialSet.isPending ? 'Leaving...' : 'Leave'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
</>
|
||||
}
|
||||
confirm={{
|
||||
label: 'Leave',
|
||||
onClick: confirmLeave,
|
||||
pending: leaveCredentialSet.isPending,
|
||||
pendingLabel: 'Leaving...',
|
||||
}}
|
||||
/>
|
||||
|
||||
<ChipModal
|
||||
<ChipConfirmModal
|
||||
open={!!deletingSet}
|
||||
onOpenChange={() => setDeletingSet(null)}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) setDeletingSet(null)
|
||||
}}
|
||||
srTitle='Delete Polling Group'
|
||||
>
|
||||
<ChipModalHeader showDivider={false}>Delete Polling Group</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
title='Delete Polling Group'
|
||||
description={
|
||||
<>
|
||||
Are you sure you want to delete{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>{deletingSet?.name}</span>?{' '}
|
||||
This action cannot be undone.
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={() => setDeletingSet(null)}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='destructive'
|
||||
flush
|
||||
onClick={confirmDelete}
|
||||
disabled={deleteCredentialSet.isPending}
|
||||
>
|
||||
{deleteCredentialSet.isPending ? 'Deleting...' : 'Delete'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
</>
|
||||
}
|
||||
confirm={{
|
||||
label: 'Delete',
|
||||
onClick: confirmDelete,
|
||||
pending: deleteCredentialSet.isPending,
|
||||
pendingLabel: 'Deleting...',
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
+12
-26
@@ -5,15 +5,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { getErrorMessage } from '@sim/utils/errors'
|
||||
import { Plus } from 'lucide-react'
|
||||
import { useParams } from 'next/navigation'
|
||||
import {
|
||||
Chip,
|
||||
ChipInput,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalFooter,
|
||||
ChipModalHeader,
|
||||
Search,
|
||||
} from '@/components/emcn'
|
||||
import { Chip, ChipConfirmModal, ChipInput, Search } from '@/components/emcn'
|
||||
import { CustomToolModal } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/components/custom-tool-modal/custom-tool-modal'
|
||||
import { useCustomTools, useDeleteCustomTool } from '@/hooks/queries/custom-tools'
|
||||
|
||||
@@ -187,28 +179,22 @@ export function CustomTools() {
|
||||
}
|
||||
/>
|
||||
|
||||
<ChipModal
|
||||
<ChipConfirmModal
|
||||
open={showDeleteDialog}
|
||||
onOpenChange={setShowDeleteDialog}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) setShowDeleteDialog(false)
|
||||
}}
|
||||
srTitle='Delete Custom Tool'
|
||||
>
|
||||
<ChipModalHeader showDivider={false}>Delete Custom Tool</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
title='Delete Custom Tool'
|
||||
description={
|
||||
<>
|
||||
Are you sure you want to delete{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>{toolToDelete?.name}</span>?{' '}
|
||||
This action cannot be undone.
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={() => setShowDeleteDialog(false)}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip variant='destructive' flush onClick={handleDeleteTool}>
|
||||
Delete
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
</>
|
||||
}
|
||||
confirm={{ label: 'Delete', onClick: handleDeleteTool }}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -530,28 +530,19 @@ export function General() {
|
||||
</p>
|
||||
<ChipModalError>{resetPassword.error?.message}</ChipModalError>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip
|
||||
variant='filled'
|
||||
flush
|
||||
onClick={() => setShowResetPasswordModal(false)}
|
||||
disabled={resetPassword.isPending || resetPassword.isSuccess}
|
||||
>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='primary'
|
||||
flush
|
||||
onClick={handleResetPasswordConfirm}
|
||||
disabled={resetPassword.isPending || resetPassword.isSuccess}
|
||||
>
|
||||
{resetPassword.isPending
|
||||
<ChipModalFooter
|
||||
onCancel={() => setShowResetPasswordModal(false)}
|
||||
cancelDisabled={resetPassword.isPending || resetPassword.isSuccess}
|
||||
primaryAction={{
|
||||
label: resetPassword.isPending
|
||||
? 'Sending...'
|
||||
: resetPassword.isSuccess
|
||||
? 'Sent'
|
||||
: 'Send Reset Email'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
: 'Send Reset Email',
|
||||
onClick: handleResetPasswordConfirm,
|
||||
disabled: resetPassword.isPending || resetPassword.isSuccess,
|
||||
}}
|
||||
/>
|
||||
</ChipModal>
|
||||
</div>
|
||||
)
|
||||
|
||||
+29
-37
@@ -4,7 +4,7 @@ import { useCallback, useState } from 'react'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { useParams } from 'next/navigation'
|
||||
import {
|
||||
Chip,
|
||||
ChipConfirmModal,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalField,
|
||||
@@ -95,20 +95,23 @@ export function InboxEnableToggle() {
|
||||
Leave blank for an auto-generated address.
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={() => setIsEnableOpen(false)}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip variant='primary' flush onClick={handleEnable} disabled={toggleInbox.isPending}>
|
||||
Enable
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
<ChipModalFooter
|
||||
onCancel={() => setIsEnableOpen(false)}
|
||||
primaryAction={{
|
||||
label: 'Enable',
|
||||
onClick: handleEnable,
|
||||
disabled: toggleInbox.isPending,
|
||||
}}
|
||||
/>
|
||||
</ChipModal>
|
||||
|
||||
<ChipModal open={isDisableOpen} onOpenChange={setIsDisableOpen} srTitle='Disable email inbox'>
|
||||
<ChipModalHeader showDivider={false}>Disable email inbox</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
<ChipConfirmModal
|
||||
open={isDisableOpen}
|
||||
onOpenChange={setIsDisableOpen}
|
||||
srTitle='Disable email inbox'
|
||||
title='Disable email inbox'
|
||||
description={
|
||||
<>
|
||||
Are you sure you want to disable the inbox
|
||||
{config?.address && (
|
||||
<>
|
||||
@@ -118,30 +121,19 @@ export function InboxEnableToggle() {
|
||||
)}
|
||||
? Any emails sent to this address after disabling will not be delivered. This action
|
||||
cannot be undone.
|
||||
</p>
|
||||
<p className='mt-2 px-2 text-[var(--text-secondary)] text-sm'>
|
||||
Your existing conversations and task history will be preserved.
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip
|
||||
variant='filled'
|
||||
flush
|
||||
disabled={toggleInbox.isPending}
|
||||
onClick={() => setIsDisableOpen(false)}
|
||||
>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='destructive'
|
||||
flush
|
||||
disabled={toggleInbox.isPending}
|
||||
onClick={handleDisable}
|
||||
>
|
||||
{toggleInbox.isPending ? 'Disabling...' : 'Disable inbox'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
</>
|
||||
}
|
||||
confirm={{
|
||||
label: 'Disable inbox',
|
||||
onClick: handleDisable,
|
||||
pending: toggleInbox.isPending,
|
||||
pendingLabel: 'Disabling...',
|
||||
}}
|
||||
>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
Your existing conversations and task history will be preserved.
|
||||
</p>
|
||||
</ChipConfirmModal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
+21
-33
@@ -195,7 +195,6 @@ export function InboxSettingsTab() {
|
||||
<Chip
|
||||
variant='ghost'
|
||||
flush
|
||||
className='text-[var(--text-muted)] hover-hover:text-[var(--text-error)]'
|
||||
leftIcon={Trash2}
|
||||
aria-label='Remove sender'
|
||||
onClick={() => handleRemoveSender(sender.id)}
|
||||
@@ -265,19 +264,14 @@ export function InboxSettingsTab() {
|
||||
/>
|
||||
<ChipModalError>{addSenderError}</ChipModalError>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={() => setIsAddSenderOpen(false)}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='primary'
|
||||
flush
|
||||
onClick={handleAddSender}
|
||||
disabled={!newSenderEmail.trim() || addSender.isPending}
|
||||
>
|
||||
Add
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
<ChipModalFooter
|
||||
onCancel={() => setIsAddSenderOpen(false)}
|
||||
primaryAction={{
|
||||
label: 'Add',
|
||||
onClick: handleAddSender,
|
||||
disabled: !newSenderEmail.trim() || addSender.isPending,
|
||||
}}
|
||||
/>
|
||||
</ChipModal>
|
||||
|
||||
<ChipModal
|
||||
@@ -285,7 +279,9 @@ export function InboxSettingsTab() {
|
||||
onOpenChange={setIsEditAddressOpen}
|
||||
srTitle='Change email address'
|
||||
>
|
||||
<ChipModalHeader showDivider={false}>Change email address</ChipModalHeader>
|
||||
<ChipModalHeader onClose={() => setIsEditAddressOpen(false)}>
|
||||
Change email address
|
||||
</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
Changing your email address will create a new inbox.{' '}
|
||||
@@ -310,24 +306,16 @@ export function InboxSettingsTab() {
|
||||
)}
|
||||
</div>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip
|
||||
variant='filled'
|
||||
flush
|
||||
disabled={updateAddress.isPending}
|
||||
onClick={() => setIsEditAddressOpen(false)}
|
||||
>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='destructive'
|
||||
flush
|
||||
onClick={handleEditAddress}
|
||||
disabled={!newUsername.trim() || updateAddress.isPending}
|
||||
>
|
||||
{updateAddress.isPending ? 'Updating...' : 'Change address'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
<ChipModalFooter
|
||||
onCancel={() => setIsEditAddressOpen(false)}
|
||||
cancelDisabled={updateAddress.isPending}
|
||||
primaryAction={{
|
||||
label: updateAddress.isPending ? 'Updating...' : 'Change address',
|
||||
onClick: handleEditAddress,
|
||||
disabled: !newUsername.trim() || updateAddress.isPending,
|
||||
variant: 'destructive',
|
||||
}}
|
||||
/>
|
||||
</ChipModal>
|
||||
</>
|
||||
)
|
||||
|
||||
+39
-57
@@ -6,11 +6,11 @@ import { getErrorMessage } from '@sim/utils/errors'
|
||||
import { ChevronDown, ChevronRight } from 'lucide-react'
|
||||
import {
|
||||
Button,
|
||||
Chip,
|
||||
ChipInput,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalFooter,
|
||||
type ChipModalFooterAction,
|
||||
ChipModalHeader,
|
||||
ChipTextarea,
|
||||
SecretInput,
|
||||
@@ -612,6 +612,40 @@ export function McpServerFormModal({
|
||||
const title = mode === 'add' ? 'Add New MCP Server' : 'Edit MCP Server'
|
||||
const submitLabel = mode === 'add' ? 'Add MCP' : 'Save'
|
||||
|
||||
const handleToggleJsonMode = () => {
|
||||
if (testResult) clearTestResult()
|
||||
setFormMode(formMode === 'form' ? 'json' : 'form')
|
||||
setJsonError(null)
|
||||
setSubmitError(null)
|
||||
}
|
||||
|
||||
const secondaryAction: ChipModalFooterAction | undefined =
|
||||
mode === 'add'
|
||||
? {
|
||||
label: formMode === 'form' ? 'Edit JSON' : 'Edit Form',
|
||||
onClick: handleToggleJsonMode,
|
||||
}
|
||||
: formMode === 'form'
|
||||
? {
|
||||
label: testButtonLabel,
|
||||
onClick: handleTestConnection,
|
||||
disabled: isTestingConnection || !isFormValid || isDomainBlocked,
|
||||
}
|
||||
: undefined
|
||||
|
||||
const primaryAction: ChipModalFooterAction =
|
||||
formMode === 'json'
|
||||
? {
|
||||
label: isSubmitting ? 'Adding...' : submitLabel,
|
||||
onClick: handleSubmitJson,
|
||||
disabled: isSubmitting || !jsonInput.trim(),
|
||||
}
|
||||
: {
|
||||
label: isSubmitting ? (mode === 'add' ? 'Adding...' : 'Saving...') : submitLabel,
|
||||
onClick: handleSubmitForm,
|
||||
disabled: isSubmitDisabled,
|
||||
}
|
||||
|
||||
return (
|
||||
<ChipModal open={open} onOpenChange={onOpenChange} srTitle={title} size='lg'>
|
||||
<ChipModalHeader onClose={() => onOpenChange(false)}>{title}</ChipModalHeader>
|
||||
@@ -770,62 +804,10 @@ export function McpServerFormModal({
|
||||
{submitError && <p className='w-full text-[var(--text-error)] text-small'>{submitError}</p>}
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter
|
||||
leading={
|
||||
<>
|
||||
{mode === 'add' && (
|
||||
<Chip
|
||||
type='button'
|
||||
variant='filled'
|
||||
flush
|
||||
onClick={() => {
|
||||
if (testResult) clearTestResult()
|
||||
setFormMode(formMode === 'form' ? 'json' : 'form')
|
||||
setJsonError(null)
|
||||
setSubmitError(null)
|
||||
}}
|
||||
>
|
||||
{formMode === 'form' ? 'Edit JSON' : 'Edit Form'}
|
||||
</Chip>
|
||||
)}
|
||||
{mode === 'edit' && formMode === 'form' && (
|
||||
<Chip
|
||||
type='button'
|
||||
variant='filled'
|
||||
flush
|
||||
onClick={handleTestConnection}
|
||||
disabled={isTestingConnection || !isFormValid || isDomainBlocked}
|
||||
>
|
||||
{testButtonLabel}
|
||||
</Chip>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Chip type='button' variant='filled' flush onClick={() => onOpenChange(false)}>
|
||||
Cancel
|
||||
</Chip>
|
||||
{formMode === 'json' ? (
|
||||
<Chip
|
||||
type='button'
|
||||
variant='primary'
|
||||
flush
|
||||
onClick={handleSubmitJson}
|
||||
disabled={isSubmitting || !jsonInput.trim()}
|
||||
>
|
||||
{isSubmitting ? 'Adding...' : submitLabel}
|
||||
</Chip>
|
||||
) : (
|
||||
<Chip
|
||||
type='button'
|
||||
variant='primary'
|
||||
flush
|
||||
onClick={handleSubmitForm}
|
||||
disabled={isSubmitDisabled}
|
||||
>
|
||||
{isSubmitting ? (mode === 'add' ? 'Adding...' : 'Saving...') : submitLabel}
|
||||
</Chip>
|
||||
)}
|
||||
</ChipModalFooter>
|
||||
onCancel={() => onOpenChange(false)}
|
||||
secondaryAction={secondaryAction}
|
||||
primaryAction={primaryAction}
|
||||
/>
|
||||
</ChipModal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,11 +9,8 @@ import {
|
||||
Badge,
|
||||
Button,
|
||||
Chip,
|
||||
ChipConfirmModal,
|
||||
ChipInput,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalFooter,
|
||||
ChipModalHeader,
|
||||
Search,
|
||||
Tooltip,
|
||||
} from '@/components/emcn'
|
||||
@@ -690,32 +687,24 @@ export function MCP({ initialServerId }: MCPProps) {
|
||||
allowedMcpDomains={allowedMcpDomains}
|
||||
/>
|
||||
|
||||
<ChipModal
|
||||
<ChipConfirmModal
|
||||
open={showDeleteDialog}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) setServerToDeleteId(null)
|
||||
}}
|
||||
srTitle='Delete MCP Server'
|
||||
>
|
||||
<ChipModalHeader showDivider={false}>Delete MCP Server</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
title='Delete MCP Server'
|
||||
description={
|
||||
<>
|
||||
Are you sure you want to delete{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>
|
||||
{servers.find((s) => s.id === serverToDeleteId)?.name || 'this server'}
|
||||
</span>
|
||||
? This action cannot be undone.
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={() => setServerToDeleteId(null)}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip variant='destructive' flush onClick={confirmDeleteServer}>
|
||||
Delete
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
</>
|
||||
}
|
||||
confirm={{ label: 'Delete', onClick: confirmDeleteServer }}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
+9
-19
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
Button,
|
||||
Chip,
|
||||
ChipInput,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
@@ -164,24 +163,15 @@ export function NoOrganizationView({
|
||||
/>
|
||||
<ChipModalError>{error}</ChipModalError>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip
|
||||
variant='filled'
|
||||
flush
|
||||
onClick={() => setCreateOrgDialogOpen(false)}
|
||||
disabled={isCreatingOrg}
|
||||
>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='primary'
|
||||
flush
|
||||
onClick={onCreateOrganization}
|
||||
disabled={isCreatingOrg || !orgName.trim()}
|
||||
>
|
||||
{isCreatingOrg ? 'Creating...' : 'Create Organization'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
<ChipModalFooter
|
||||
onCancel={() => setCreateOrgDialogOpen(false)}
|
||||
cancelDisabled={isCreatingOrg}
|
||||
primaryAction={{
|
||||
label: isCreatingOrg ? 'Creating...' : 'Create Organization',
|
||||
onClick: onCreateOrganization,
|
||||
disabled: isCreatingOrg || !orgName.trim(),
|
||||
}}
|
||||
/>
|
||||
</ChipModal>
|
||||
</div>
|
||||
)
|
||||
|
||||
+8
-6
@@ -3,7 +3,6 @@
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import {
|
||||
Chip,
|
||||
ChipDropdown,
|
||||
type ChipDropdownOption,
|
||||
ChipModal,
|
||||
@@ -169,11 +168,14 @@ export function OrganizationInviteModal({
|
||||
onChange={(role) => setInviteRole(role as PermissionType)}
|
||||
/>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='primary' onClick={handleSend} disabled={isSendDisabled}>
|
||||
{isSubmitting ? 'Sending...' : 'Send invites'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
<ChipModalFooter
|
||||
onCancel={() => handleOpenChange(false)}
|
||||
primaryAction={{
|
||||
label: isSubmitting ? 'Sending...' : 'Send invites',
|
||||
onClick: handleSend,
|
||||
disabled: isSendDisabled,
|
||||
}}
|
||||
/>
|
||||
</ChipModal>
|
||||
)
|
||||
}
|
||||
|
||||
+27
-28
@@ -1,11 +1,4 @@
|
||||
import {
|
||||
Chip,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalError,
|
||||
ChipModalFooter,
|
||||
ChipModalHeader,
|
||||
} from '@/components/emcn'
|
||||
import { ChipConfirmModal } from '@/components/emcn'
|
||||
|
||||
interface RemoveMemberDialogProps {
|
||||
open: boolean
|
||||
@@ -36,13 +29,20 @@ export function RemoveMemberDialog({
|
||||
? 'Remove External Member'
|
||||
: 'Remove Team Member'
|
||||
|
||||
const errorMessage =
|
||||
error instanceof Error && error.message ? error.message : error ? String(error) : null
|
||||
|
||||
return (
|
||||
<ChipModal open={open} onOpenChange={onOpenChange} srTitle={title}>
|
||||
<ChipModalHeader onClose={() => onOpenChange(false)} showDivider={false}>
|
||||
{title}
|
||||
</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
<ChipConfirmModal
|
||||
open={open}
|
||||
onOpenChange={(next) => {
|
||||
if (!next) onCancel()
|
||||
onOpenChange(next)
|
||||
}}
|
||||
srTitle={title}
|
||||
title={title}
|
||||
description={
|
||||
<>
|
||||
{isSelfRemoval ? (
|
||||
'Are you sure you want to leave this organization? You will lose access to all team resources.'
|
||||
) : isExternalRemoval ? (
|
||||
@@ -60,20 +60,19 @@ export function RemoveMemberDialog({
|
||||
</>
|
||||
)}{' '}
|
||||
This action cannot be undone.
|
||||
</>
|
||||
}
|
||||
confirm={{
|
||||
label: isSelfRemoval ? 'Leave Organization' : 'Remove',
|
||||
onClick: () => onConfirmRemove(),
|
||||
pending: isSubmitting,
|
||||
}}
|
||||
>
|
||||
{errorMessage ? (
|
||||
<p role='alert' className='mt-1 px-2 text-[var(--text-error)] text-caption'>
|
||||
{errorMessage}
|
||||
</p>
|
||||
|
||||
<ChipModalError>
|
||||
{error instanceof Error && error.message ? error.message : error ? String(error) : null}
|
||||
</ChipModalError>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush disabled={isSubmitting} onClick={onCancel}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip variant='destructive' flush disabled={isSubmitting} onClick={() => onConfirmRemove()}>
|
||||
{isSelfRemoval ? 'Leave Organization' : 'Remove'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
) : null}
|
||||
</ChipConfirmModal>
|
||||
)
|
||||
}
|
||||
|
||||
+17
-23
@@ -7,12 +7,8 @@ import {
|
||||
AvatarImage,
|
||||
Badge,
|
||||
Banner,
|
||||
Chip,
|
||||
ChipConfirmModal,
|
||||
ChipInput,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalFooter,
|
||||
ChipModalHeader,
|
||||
Search,
|
||||
Skeleton,
|
||||
} from '@/components/emcn'
|
||||
@@ -86,9 +82,20 @@ export function TransferOwnershipDialog({
|
||||
}
|
||||
|
||||
return (
|
||||
<ChipModal open={open} onOpenChange={handleClose} srTitle='Leave organization'>
|
||||
<ChipModalHeader showDivider={false}>Leave organization</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<ChipConfirmModal
|
||||
open={open}
|
||||
onOpenChange={handleClose}
|
||||
srTitle='Leave organization'
|
||||
title='Leave organization'
|
||||
confirm={{
|
||||
label: 'Transfer & leave',
|
||||
onClick: handleConfirm,
|
||||
pending: isSubmitting,
|
||||
pendingLabel: 'Transferring...',
|
||||
disabled: !selectedUserId || !hasCandidates || isLoadingMembers,
|
||||
}}
|
||||
>
|
||||
<div className='flex flex-col gap-4'>
|
||||
{isLoadingMembers ? (
|
||||
<div className='space-y-3'>
|
||||
<Skeleton className='h-4 w-3/4' />
|
||||
@@ -203,20 +210,7 @@ export function TransferOwnershipDialog({
|
||||
{error instanceof Error && error.message ? error.message : String(error)}
|
||||
</p>
|
||||
)}
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={() => handleClose(false)} disabled={isSubmitting}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='destructive'
|
||||
flush
|
||||
onClick={handleConfirm}
|
||||
disabled={!selectedUserId || isSubmitting || !hasCandidates || isLoadingMembers}
|
||||
>
|
||||
{isSubmitting ? 'Transferring...' : 'Transfer & leave'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
</div>
|
||||
</ChipConfirmModal>
|
||||
)
|
||||
}
|
||||
|
||||
+8
-14
@@ -5,7 +5,6 @@ import { createLogger } from '@sim/logger'
|
||||
import {
|
||||
ButtonGroup,
|
||||
ButtonGroupItem,
|
||||
Chip,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalError,
|
||||
@@ -129,19 +128,14 @@ export function CreateWorkflowMcpServerModal({
|
||||
</ChipModalField>
|
||||
<ChipModalError>{createServerMutation.error?.message}</ChipModalError>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={() => onOpenChange(false)}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='primary'
|
||||
flush
|
||||
onClick={handleCreateServer}
|
||||
disabled={!isFormValid || createServerMutation.isPending}
|
||||
>
|
||||
{createServerMutation.isPending ? 'Adding...' : 'Add Server'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
<ChipModalFooter
|
||||
onCancel={() => onOpenChange(false)}
|
||||
primaryAction={{
|
||||
label: createServerMutation.isPending ? 'Adding...' : 'Add Server',
|
||||
onClick: handleCreateServer,
|
||||
disabled: !isFormValid || createServerMutation.isPending,
|
||||
}}
|
||||
/>
|
||||
</ChipModal>
|
||||
)
|
||||
}
|
||||
|
||||
+106
-139
@@ -11,6 +11,7 @@ import {
|
||||
ButtonGroup,
|
||||
ButtonGroupItem,
|
||||
Chip,
|
||||
ChipConfirmModal,
|
||||
ChipInput,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
@@ -159,6 +160,62 @@ function ServerDetailView({ workspaceId, serverId, onBack }: ServerDetailViewPro
|
||||
}
|
||||
}
|
||||
|
||||
const handleSaveToolEdit = async () => {
|
||||
if (!toolToView) return
|
||||
try {
|
||||
const currentSchema = toolToView.parameterSchema as Record<string, unknown>
|
||||
const currentProperties = (currentSchema?.properties || {}) as Record<
|
||||
string,
|
||||
{ type?: string; description?: string }
|
||||
>
|
||||
const updatedProperties: Record<string, { type?: string; description?: string }> = {}
|
||||
|
||||
for (const [name, prop] of Object.entries(currentProperties)) {
|
||||
updatedProperties[name] = {
|
||||
...prop,
|
||||
description: editingParameterDescriptions[name]?.trim() || undefined,
|
||||
}
|
||||
}
|
||||
|
||||
const updatedSchema = {
|
||||
...currentSchema,
|
||||
properties: updatedProperties,
|
||||
}
|
||||
|
||||
await updateToolMutation.mutateAsync({
|
||||
workspaceId,
|
||||
serverId,
|
||||
toolId: toolToView.id,
|
||||
toolDescription: editingDescription.trim() || undefined,
|
||||
parameterSchema: updatedSchema,
|
||||
})
|
||||
setToolToView(null)
|
||||
setEditingDescription('')
|
||||
setEditingParameterDescriptions({})
|
||||
} catch (err) {
|
||||
logger.error('Failed to update tool:', err)
|
||||
}
|
||||
}
|
||||
|
||||
const isSaveToolDisabled = (() => {
|
||||
if (updateToolMutation.isPending) return true
|
||||
if (!toolToView) return true
|
||||
|
||||
const descriptionChanged = editingDescription.trim() !== (toolToView.toolDescription || '')
|
||||
|
||||
const schema = toolToView.parameterSchema as
|
||||
| { properties?: Record<string, { type?: string; description?: string }> }
|
||||
| undefined
|
||||
const properties = schema?.properties || {}
|
||||
const paramDescriptionsChanged = Object.keys(properties).some((name) => {
|
||||
const original = properties[name]?.description || ''
|
||||
const edited = editingParameterDescriptions[name]?.trim() || ''
|
||||
return original !== edited
|
||||
})
|
||||
|
||||
return !descriptionChanged && !paramDescriptionsChanged
|
||||
})()
|
||||
|
||||
const tools = data?.tools ?? []
|
||||
|
||||
const availableWorkflows = useMemo(() => {
|
||||
@@ -620,33 +677,25 @@ function ServerDetailView({ workspaceId, serverId, onBack }: ServerDetailViewPro
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ChipModal
|
||||
<ChipConfirmModal
|
||||
open={!!toolToDelete}
|
||||
onOpenChange={(open) => !open && setToolToDelete(null)}
|
||||
srTitle='Remove Workflow'
|
||||
>
|
||||
<ChipModalHeader showDivider={false}>Remove Workflow</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
title='Remove Workflow'
|
||||
description={
|
||||
<>
|
||||
Are you sure you want to remove{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>{toolToDelete?.toolName}</span>{' '}
|
||||
from this server? The workflow will remain deployed and can be added back later.
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={() => setToolToDelete(null)}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='destructive'
|
||||
flush
|
||||
onClick={handleDeleteTool}
|
||||
disabled={deleteToolMutation.isPending}
|
||||
>
|
||||
{deleteToolMutation.isPending ? 'Removing...' : 'Remove'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
</>
|
||||
}
|
||||
confirm={{
|
||||
label: 'Remove',
|
||||
onClick: handleDeleteTool,
|
||||
pending: deleteToolMutation.isPending,
|
||||
pendingLabel: 'Removing...',
|
||||
}}
|
||||
/>
|
||||
|
||||
<ChipModal
|
||||
open={!!toolToView}
|
||||
@@ -722,73 +771,14 @@ function ServerDetailView({ workspaceId, serverId, onBack }: ServerDetailViewPro
|
||||
})()}
|
||||
</ChipModalField>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={() => setToolToView(null)}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='primary'
|
||||
flush
|
||||
onClick={async () => {
|
||||
if (!toolToView) return
|
||||
try {
|
||||
const currentSchema = toolToView.parameterSchema as Record<string, unknown>
|
||||
const currentProperties = (currentSchema?.properties || {}) as Record<
|
||||
string,
|
||||
{ type?: string; description?: string }
|
||||
>
|
||||
const updatedProperties: Record<string, { type?: string; description?: string }> =
|
||||
{}
|
||||
|
||||
for (const [name, prop] of Object.entries(currentProperties)) {
|
||||
updatedProperties[name] = {
|
||||
...prop,
|
||||
description: editingParameterDescriptions[name]?.trim() || undefined,
|
||||
}
|
||||
}
|
||||
|
||||
const updatedSchema = {
|
||||
...currentSchema,
|
||||
properties: updatedProperties,
|
||||
}
|
||||
|
||||
await updateToolMutation.mutateAsync({
|
||||
workspaceId,
|
||||
serverId,
|
||||
toolId: toolToView.id,
|
||||
toolDescription: editingDescription.trim() || undefined,
|
||||
parameterSchema: updatedSchema,
|
||||
})
|
||||
setToolToView(null)
|
||||
setEditingDescription('')
|
||||
setEditingParameterDescriptions({})
|
||||
} catch (err) {
|
||||
logger.error('Failed to update tool:', err)
|
||||
}
|
||||
}}
|
||||
disabled={(() => {
|
||||
if (updateToolMutation.isPending) return true
|
||||
if (!toolToView) return true
|
||||
|
||||
const descriptionChanged =
|
||||
editingDescription.trim() !== (toolToView.toolDescription || '')
|
||||
|
||||
const schema = toolToView.parameterSchema as
|
||||
| { properties?: Record<string, { type?: string; description?: string }> }
|
||||
| undefined
|
||||
const properties = schema?.properties || {}
|
||||
const paramDescriptionsChanged = Object.keys(properties).some((name) => {
|
||||
const original = properties[name]?.description || ''
|
||||
const edited = editingParameterDescriptions[name]?.trim() || ''
|
||||
return original !== edited
|
||||
})
|
||||
|
||||
return !descriptionChanged && !paramDescriptionsChanged
|
||||
})()}
|
||||
>
|
||||
{updateToolMutation.isPending ? 'Saving...' : 'Save'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
<ChipModalFooter
|
||||
onCancel={() => setToolToView(null)}
|
||||
primaryAction={{
|
||||
label: updateToolMutation.isPending ? 'Saving...' : 'Save',
|
||||
onClick: handleSaveToolEdit,
|
||||
disabled: isSaveToolDisabled,
|
||||
}}
|
||||
/>
|
||||
</ChipModal>
|
||||
|
||||
<ChipModal
|
||||
@@ -834,26 +824,17 @@ function ServerDetailView({ workspaceId, serverId, onBack }: ServerDetailViewPro
|
||||
: null}
|
||||
</ChipModalError>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip
|
||||
variant='filled'
|
||||
flush
|
||||
onClick={() => {
|
||||
setShowAddWorkflow(false)
|
||||
setSelectedWorkflowId(null)
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='primary'
|
||||
flush
|
||||
onClick={handleAddWorkflow}
|
||||
disabled={!selectedWorkflowId || addToolMutation.isPending}
|
||||
>
|
||||
{addToolMutation.isPending ? 'Adding...' : 'Add Workflow'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
<ChipModalFooter
|
||||
onCancel={() => {
|
||||
setShowAddWorkflow(false)
|
||||
setSelectedWorkflowId(null)
|
||||
}}
|
||||
primaryAction={{
|
||||
label: addToolMutation.isPending ? 'Adding...' : 'Add Workflow',
|
||||
onClick: handleAddWorkflow,
|
||||
disabled: !selectedWorkflowId || addToolMutation.isPending,
|
||||
}}
|
||||
/>
|
||||
</ChipModal>
|
||||
|
||||
<ChipModal
|
||||
@@ -900,25 +881,19 @@ function ServerDetailView({ workspaceId, serverId, onBack }: ServerDetailViewPro
|
||||
</div>
|
||||
</ChipModalField>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={() => setShowEditServer(false)}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='primary'
|
||||
flush
|
||||
onClick={handleSaveServerEdit}
|
||||
disabled={
|
||||
<ChipModalFooter
|
||||
onCancel={() => setShowEditServer(false)}
|
||||
primaryAction={{
|
||||
label: updateServerMutation.isPending ? 'Saving...' : 'Save',
|
||||
onClick: handleSaveServerEdit,
|
||||
disabled:
|
||||
!editServerName.trim() ||
|
||||
updateServerMutation.isPending ||
|
||||
(editServerName === server.name &&
|
||||
editServerDescription === (server.description || '') &&
|
||||
editServerIsPublic === server.isPublic)
|
||||
}
|
||||
>
|
||||
{updateServerMutation.isPending ? 'Saving...' : 'Save'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
editServerIsPublic === server.isPublic),
|
||||
}}
|
||||
/>
|
||||
</ChipModal>
|
||||
|
||||
<CreateApiKeyModal
|
||||
@@ -1090,28 +1065,20 @@ export function WorkflowMcpServers() {
|
||||
isLoadingWorkflows={isLoadingWorkflows}
|
||||
/>
|
||||
|
||||
<ChipModal
|
||||
<ChipConfirmModal
|
||||
open={!!serverToDelete}
|
||||
onOpenChange={(open) => !open && setServerToDelete(null)}
|
||||
srTitle='Delete MCP Server'
|
||||
>
|
||||
<ChipModalHeader showDivider={false}>Delete MCP Server</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
title='Delete MCP Server'
|
||||
description={
|
||||
<>
|
||||
Are you sure you want to delete{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>{serverToDelete?.name}</span>?
|
||||
This action cannot be undone.
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={() => setServerToDelete(null)}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip variant='destructive' flush onClick={handleDeleteServer}>
|
||||
Delete
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
</>
|
||||
}
|
||||
confirm={{ label: 'Delete', onClick: handleDeleteServer }}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
+15
-15
@@ -3,7 +3,6 @@
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { useParams } from 'next/navigation'
|
||||
import {
|
||||
Chip,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalError,
|
||||
@@ -222,21 +221,22 @@ export function SkillModal({
|
||||
|
||||
{showFooter && (
|
||||
<ChipModalFooter
|
||||
leading={
|
||||
isEditing && onDelete ? (
|
||||
<Chip variant='destructive' flush onClick={() => onDelete(initialValues.id)}>
|
||||
Delete
|
||||
</Chip>
|
||||
) : undefined
|
||||
onCancel={() => onOpenChange(false)}
|
||||
secondaryAction={
|
||||
isEditing && onDelete
|
||||
? {
|
||||
label: 'Delete',
|
||||
onClick: () => onDelete(initialValues.id),
|
||||
variant: 'destructive',
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<Chip variant='filled' flush onClick={() => onOpenChange(false)}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip variant='primary' flush onClick={handleSave} disabled={saving || !hasChanges}>
|
||||
{saving ? 'Saving...' : isEditing ? 'Update' : 'Create'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
primaryAction={{
|
||||
label: saving ? 'Saving...' : isEditing ? 'Update' : 'Create',
|
||||
onClick: handleSave,
|
||||
disabled: saving || !hasChanges,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</ChipModal>
|
||||
)
|
||||
|
||||
@@ -5,15 +5,7 @@ import { createLogger } from '@sim/logger'
|
||||
import { getErrorMessage } from '@sim/utils/errors'
|
||||
import { ArrowRight, Plus } from 'lucide-react'
|
||||
import { useParams } from 'next/navigation'
|
||||
import {
|
||||
Chip,
|
||||
ChipInput,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalFooter,
|
||||
ChipModalHeader,
|
||||
Search,
|
||||
} from '@/components/emcn'
|
||||
import { Chip, ChipConfirmModal, ChipInput, Search } from '@/components/emcn'
|
||||
import { SkillTile } from '@/app/workspace/[workspaceId]/components'
|
||||
import { IntegrationTabsHeader } from '@/app/workspace/[workspaceId]/integrations/components/integration-tabs-header'
|
||||
import { ShowcaseWithExplore } from '@/app/workspace/[workspaceId]/integrations/components/showcase-with-explore'
|
||||
@@ -200,24 +192,23 @@ export function Skills() {
|
||||
initialValues={editingSkill ?? undefined}
|
||||
/>
|
||||
|
||||
<ChipModal open={showDeleteDialog} onOpenChange={setShowDeleteDialog} srTitle='Delete Skill'>
|
||||
<ChipModalHeader showDivider={false}>Delete Skill</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
<ChipConfirmModal
|
||||
open={showDeleteDialog}
|
||||
onOpenChange={setShowDeleteDialog}
|
||||
srTitle='Delete Skill'
|
||||
title='Delete Skill'
|
||||
description={
|
||||
<>
|
||||
Are you sure you want to delete{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>{skillToDelete?.name}</span>?{' '}
|
||||
This action cannot be undone.
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={() => setShowDeleteDialog(false)}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip variant='destructive' flush onClick={handleDeleteSkill}>
|
||||
Delete
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
</>
|
||||
}
|
||||
confirm={{
|
||||
label: 'Delete',
|
||||
onClick: handleDeleteSkill,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
+32
-35
@@ -6,7 +6,7 @@ import { getErrorMessage } from '@sim/utils/errors'
|
||||
import { useParams } from 'next/navigation'
|
||||
import {
|
||||
Checkbox,
|
||||
Chip,
|
||||
ChipConfirmModal,
|
||||
ChipInput,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
@@ -103,8 +103,8 @@ export function RowModal({ mode, isOpen, onClose, table, row, rowIds, onSuccess
|
||||
deleteRowMutation.isPending ||
|
||||
deleteRowsMutation.isPending
|
||||
|
||||
const handleFormSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
const handleFormSubmit = async (e?: React.FormEvent) => {
|
||||
e?.preventDefault()
|
||||
setError(null)
|
||||
|
||||
try {
|
||||
@@ -153,38 +153,33 @@ export function RowModal({ mode, isOpen, onClose, table, row, rowIds, onSuccess
|
||||
const isSingleRow = deleteCount === 1
|
||||
|
||||
return (
|
||||
<ChipModal
|
||||
<ChipConfirmModal
|
||||
open={isOpen}
|
||||
onOpenChange={handleClose}
|
||||
srTitle={`Delete ${isSingleRow ? 'Row' : `${deleteCount} Rows`}`}
|
||||
>
|
||||
<ChipModalHeader showDivider={false}>
|
||||
Delete {isSingleRow ? 'Row' : `${deleteCount} Rows`}
|
||||
</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
{error && (
|
||||
<div className='rounded-lg border border-[var(--status-error-border)] bg-[var(--status-error-bg)] px-3.5 py-3 text-[var(--status-error-text)] text-small'>
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
title={`Delete ${isSingleRow ? 'Row' : `${deleteCount} Rows`}`}
|
||||
description={
|
||||
<>
|
||||
{error && (
|
||||
<span className='mb-3 block rounded-lg border border-[var(--status-error-border)] bg-[var(--status-error-bg)] px-3.5 py-3 text-[var(--status-error-text)] text-small'>
|
||||
{error}
|
||||
</span>
|
||||
)}
|
||||
Are you sure you want to delete {isSingleRow ? 'this row' : `these ${deleteCount} rows`}
|
||||
?{' '}
|
||||
<span className='text-[var(--text-error)]'>
|
||||
This will permanently remove all data in {isSingleRow ? 'this row' : 'these rows'}.
|
||||
</span>{' '}
|
||||
This action cannot be undone.
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={handleClose} disabled={isSubmitting}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip variant='destructive' flush onClick={handleDelete} disabled={isSubmitting}>
|
||||
{isSubmitting ? 'Deleting...' : 'Delete'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
</>
|
||||
}
|
||||
confirm={{
|
||||
label: 'Delete',
|
||||
onClick: handleDelete,
|
||||
pending: isSubmitting,
|
||||
pendingLabel: 'Deleting...',
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -207,6 +202,7 @@ export function RowModal({ mode, isOpen, onClose, table, row, rowIds, onSuccess
|
||||
</ChipModalHeader>
|
||||
<ChipModalBody className='max-h-[60vh] overflow-y-auto'>
|
||||
<form onSubmit={handleFormSubmit} className='flex flex-col gap-4'>
|
||||
<button type='submit' hidden disabled={isSubmitting} />
|
||||
<ErrorMessage error={error} />
|
||||
|
||||
{columns.map((column) => (
|
||||
@@ -219,20 +215,21 @@ export function RowModal({ mode, isOpen, onClose, table, row, rowIds, onSuccess
|
||||
))}
|
||||
</form>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={handleClose} disabled={isSubmitting}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip variant='primary' flush onClick={handleFormSubmit} disabled={isSubmitting}>
|
||||
{isSubmitting
|
||||
<ChipModalFooter
|
||||
onCancel={handleClose}
|
||||
cancelDisabled={isSubmitting}
|
||||
primaryAction={{
|
||||
label: isSubmitting
|
||||
? isAddMode
|
||||
? 'Adding...'
|
||||
: 'Updating...'
|
||||
: isAddMode
|
||||
? 'Add Row'
|
||||
: 'Update Row'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
: 'Update Row',
|
||||
onClick: handleFormSubmit,
|
||||
disabled: isSubmitting,
|
||||
}}
|
||||
/>
|
||||
</ChipModal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,14 +4,7 @@ import { useCallback, useMemo, useReducer, useRef, useState } from 'react'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { useParams, useRouter } from 'next/navigation'
|
||||
import { usePostHog } from 'posthog-js/react'
|
||||
import {
|
||||
Chip,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalFooter,
|
||||
ChipModalHeader,
|
||||
toast,
|
||||
} from '@/components/emcn'
|
||||
import { ChipConfirmModal, toast } from '@/components/emcn'
|
||||
import { Download, Pencil, Table as TableIcon, Trash, Upload } from '@/components/emcn/icons'
|
||||
import type { RunLimit, RunMode } from '@/lib/api/contracts/tables'
|
||||
import { captureEvent } from '@/lib/posthog/client'
|
||||
@@ -515,6 +508,13 @@ export function Table({
|
||||
}
|
||||
}
|
||||
|
||||
const handleConfirmDeleteColumns = () => {
|
||||
if (!deletingColumns) return
|
||||
const names = deletingColumns
|
||||
setDeletingColumns(null)
|
||||
confirmDeleteColumnsSinkRef.current?.(names)
|
||||
}
|
||||
|
||||
const columnConfig = slideout.kind === 'column' ? slideout.config : null
|
||||
const workflowConfig = slideout.kind === 'workflow' ? slideout.config : null
|
||||
const executionId = slideout.kind === 'execution' ? slideout.executionId : null
|
||||
@@ -709,7 +709,7 @@ export function Table({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<ChipModal
|
||||
<ChipConfirmModal
|
||||
open={deletingColumns !== null}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) setDeletingColumns(null)
|
||||
@@ -719,14 +719,13 @@ export function Table({
|
||||
? `Delete ${deletingColumns.length} Columns`
|
||||
: 'Delete Column'
|
||||
}
|
||||
>
|
||||
<ChipModalHeader showDivider={false}>
|
||||
{deletingColumns && deletingColumns.length > 1
|
||||
title={
|
||||
deletingColumns && deletingColumns.length > 1
|
||||
? `Delete ${deletingColumns.length} Columns`
|
||||
: 'Delete Column'}
|
||||
</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
: 'Delete Column'
|
||||
}
|
||||
description={
|
||||
<>
|
||||
{deletingColumns && deletingColumns.length > 1 ? (
|
||||
<>
|
||||
Are you sure you want to delete{' '}
|
||||
@@ -749,62 +748,36 @@ export function Table({
|
||||
{deletingColumns && deletingColumns.length > 1 ? 'these columns' : 'this column'}.
|
||||
</span>{' '}
|
||||
You can undo this action.
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={() => setDeletingColumns(null)}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='destructive'
|
||||
flush
|
||||
onClick={() => {
|
||||
if (!deletingColumns) return
|
||||
const names = deletingColumns
|
||||
setDeletingColumns(null)
|
||||
confirmDeleteColumnsSinkRef.current?.(names)
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
</>
|
||||
}
|
||||
confirm={{
|
||||
label: 'Delete',
|
||||
onClick: handleConfirmDeleteColumns,
|
||||
}}
|
||||
/>
|
||||
{!embedded && (
|
||||
<ChipModal
|
||||
<ChipConfirmModal
|
||||
open={showDeleteTableConfirm}
|
||||
onOpenChange={setShowDeleteTableConfirm}
|
||||
srTitle='Delete Table'
|
||||
>
|
||||
<ChipModalHeader showDivider={false}>Delete Table</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
title='Delete Table'
|
||||
description={
|
||||
<>
|
||||
Are you sure you want to delete{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>{tableData?.name}</span>?{' '}
|
||||
<span className='text-[var(--text-error)]'>
|
||||
All {tableData?.rowCount ?? 0} rows will be removed.
|
||||
</span>{' '}
|
||||
You can restore it from Recently Deleted in Settings.
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip
|
||||
variant='filled'
|
||||
flush
|
||||
onClick={() => setShowDeleteTableConfirm(false)}
|
||||
disabled={deleteTableMutation.isPending}
|
||||
>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='destructive'
|
||||
flush
|
||||
onClick={handleDeleteTable}
|
||||
disabled={deleteTableMutation.isPending}
|
||||
>
|
||||
{deleteTableMutation.isPending ? 'Deleting...' : 'Delete'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
</>
|
||||
}
|
||||
confirm={{
|
||||
label: 'Delete',
|
||||
onClick: handleDeleteTable,
|
||||
pending: deleteTableMutation.isPending,
|
||||
pendingLabel: 'Deleting...',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
+11
-20
@@ -7,7 +7,6 @@ import {
|
||||
Button,
|
||||
ButtonGroup,
|
||||
ButtonGroupItem,
|
||||
Chip,
|
||||
ChipCombobox,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
@@ -478,30 +477,22 @@ export function ImportCsvDialog({
|
||||
</div>
|
||||
)}
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip
|
||||
variant='filled'
|
||||
flush
|
||||
onClick={() => onOpenChange(false)}
|
||||
disabled={importMutation.isPending}
|
||||
>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant={mode === 'replace' ? 'destructive' : 'primary'}
|
||||
flush
|
||||
onClick={handleSubmit}
|
||||
disabled={!canSubmit}
|
||||
>
|
||||
{importMutation.isPending
|
||||
<ChipModalFooter
|
||||
onCancel={() => onOpenChange(false)}
|
||||
cancelDisabled={importMutation.isPending}
|
||||
primaryAction={{
|
||||
label: importMutation.isPending
|
||||
? mode === 'replace'
|
||||
? 'Replacing...'
|
||||
: 'Importing...'
|
||||
: mode === 'replace'
|
||||
? 'Replace rows'
|
||||
: 'Append rows'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
: 'Append rows',
|
||||
onClick: handleSubmit,
|
||||
disabled: !canSubmit,
|
||||
variant: mode === 'replace' ? 'destructive' : 'primary',
|
||||
}}
|
||||
/>
|
||||
</ChipModal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,16 +4,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { useParams, useRouter } from 'next/navigation'
|
||||
import type { ComboboxOption } from '@/components/emcn'
|
||||
import {
|
||||
Chip,
|
||||
ChipCombobox,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalFooter,
|
||||
ChipModalHeader,
|
||||
toast,
|
||||
Upload,
|
||||
} from '@/components/emcn'
|
||||
import { ChipCombobox, ChipConfirmModal, toast, Upload } from '@/components/emcn'
|
||||
import { Columns3, Rows3, Table as TableIcon } from '@/components/emcn/icons'
|
||||
import type { TableDefinition } from '@/lib/table'
|
||||
import { generateUniqueTableName } from '@/lib/table/constants'
|
||||
@@ -591,39 +582,31 @@ export function Tables() {
|
||||
/>
|
||||
)}
|
||||
|
||||
<ChipModal
|
||||
<ChipConfirmModal
|
||||
open={isDeleteDialogOpen}
|
||||
onOpenChange={setIsDeleteDialogOpen}
|
||||
onOpenChange={(open) => {
|
||||
setIsDeleteDialogOpen(open)
|
||||
if (!open) setActiveTable(null)
|
||||
}}
|
||||
srTitle='Delete Table'
|
||||
>
|
||||
<ChipModalHeader showDivider={false}>Delete Table</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
title='Delete Table'
|
||||
description={
|
||||
<>
|
||||
Are you sure you want to delete{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>{activeTable?.name}</span>?{' '}
|
||||
<span className='text-[var(--text-error)]'>
|
||||
All {activeTable?.rowCount} rows will be removed.
|
||||
</span>{' '}
|
||||
You can restore it from Recently Deleted in Settings.
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip
|
||||
variant='filled'
|
||||
flush
|
||||
onClick={() => {
|
||||
setIsDeleteDialogOpen(false)
|
||||
setActiveTable(null)
|
||||
}}
|
||||
disabled={deleteTable.isPending}
|
||||
>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip variant='destructive' flush onClick={handleDelete} disabled={deleteTable.isPending}>
|
||||
{deleteTable.isPending ? 'Deleting...' : 'Delete'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
</>
|
||||
}
|
||||
confirm={{
|
||||
label: 'Delete',
|
||||
onClick: handleDelete,
|
||||
pending: deleteTable.isPending,
|
||||
pendingLabel: 'Deleting...',
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ export type CommandId =
|
||||
| 'accept-diff-changes'
|
||||
| 'add-agent'
|
||||
| 'add-workflow'
|
||||
| 'add-task'
|
||||
| 'goto-logs'
|
||||
| 'open-search'
|
||||
| 'open-workflow-search-replace'
|
||||
@@ -59,11 +58,6 @@ export const COMMAND_DEFINITIONS: Record<CommandId, CommandDefinition> = {
|
||||
shortcut: 'Mod+Shift+P',
|
||||
allowInEditable: false,
|
||||
},
|
||||
'add-task': {
|
||||
id: 'add-task',
|
||||
shortcut: 'Mod+Shift+K',
|
||||
allowInEditable: false,
|
||||
},
|
||||
'goto-logs': {
|
||||
id: 'goto-logs',
|
||||
shortcut: 'Mod+L',
|
||||
|
||||
+14
-31
@@ -8,12 +8,8 @@ import {
|
||||
Button,
|
||||
ButtonGroup,
|
||||
ButtonGroupItem,
|
||||
Chip,
|
||||
ChipConfirmModal,
|
||||
ChipInput,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalFooter,
|
||||
ChipModalHeader,
|
||||
Input,
|
||||
Label,
|
||||
Loader,
|
||||
@@ -414,14 +410,13 @@ export function ChatDeploy({
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<ChipModal
|
||||
<ChipConfirmModal
|
||||
open={showDeleteConfirmation}
|
||||
onOpenChange={setShowDeleteConfirmation}
|
||||
srTitle='Delete Chat'
|
||||
>
|
||||
<ChipModalHeader showDivider={false}>Delete Chat</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
title='Delete Chat'
|
||||
description={
|
||||
<>
|
||||
Are you sure you want to delete{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>
|
||||
{existingChat?.title || 'this chat'}
|
||||
@@ -432,27 +427,15 @@ export function ChatDeploy({
|
||||
make it unavailable to all users.
|
||||
</span>{' '}
|
||||
This action cannot be undone.
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip
|
||||
variant='filled'
|
||||
flush
|
||||
onClick={() => setShowDeleteConfirmation(false)}
|
||||
disabled={deleteChatMutation.isPending}
|
||||
>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='destructive'
|
||||
flush
|
||||
onClick={handleDelete}
|
||||
disabled={deleteChatMutation.isPending}
|
||||
>
|
||||
{deleteChatMutation.isPending ? 'Deleting...' : 'Delete'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
</>
|
||||
}
|
||||
confirm={{
|
||||
label: 'Delete',
|
||||
onClick: handleDelete,
|
||||
pending: deleteChatMutation.isPending,
|
||||
pendingLabel: 'Deleting...',
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
+19
-26
@@ -7,7 +7,7 @@ import {
|
||||
Badge,
|
||||
ButtonGroup,
|
||||
ButtonGroupItem,
|
||||
Chip,
|
||||
ChipConfirmModal,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalError,
|
||||
@@ -273,36 +273,29 @@ export function ApiInfoModal({ open, onOpenChange, workflowId }: ApiInfoModalPro
|
||||
|
||||
<ChipModalError>{saveError}</ChipModalError>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={handleCloseAttempt} disabled={isSaving}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip variant='primary' flush onClick={handleSave} disabled={!hasChanges || isSaving}>
|
||||
Save
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
<ChipModalFooter
|
||||
onCancel={handleCloseAttempt}
|
||||
cancelDisabled={isSaving}
|
||||
primaryAction={{
|
||||
label: 'Save',
|
||||
onClick: handleSave,
|
||||
disabled: !hasChanges || isSaving,
|
||||
}}
|
||||
/>
|
||||
</ChipModal>
|
||||
|
||||
<ChipModal
|
||||
<ChipConfirmModal
|
||||
open={showUnsavedChangesAlert}
|
||||
onOpenChange={setShowUnsavedChangesAlert}
|
||||
srTitle='Unsaved Changes'
|
||||
>
|
||||
<ChipModalHeader showDivider={false}>Unsaved Changes</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
You have unsaved changes. Are you sure you want to discard them?
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={() => setShowUnsavedChangesAlert(false)}>
|
||||
Keep Editing
|
||||
</Chip>
|
||||
<Chip variant='destructive' flush onClick={handleDiscardChanges}>
|
||||
Discard Changes
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
title='Unsaved Changes'
|
||||
description='You have unsaved changes. Are you sure you want to discard them?'
|
||||
dismissLabel='Keep editing'
|
||||
confirm={{
|
||||
label: 'Discard Changes',
|
||||
onClick: handleDiscardChanges,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
+19
-35
@@ -3,6 +3,7 @@
|
||||
import { useRef, useState } from 'react'
|
||||
import {
|
||||
Chip,
|
||||
ChipConfirmModal,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalError,
|
||||
@@ -133,46 +134,29 @@ export function VersionDescriptionModal({
|
||||
{updateMutation.error?.message || generateMutation.error?.message}
|
||||
</ChipModalError>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip
|
||||
variant='filled'
|
||||
flush
|
||||
onClick={handleCloseAttempt}
|
||||
disabled={updateMutation.isPending || isGenerating}
|
||||
>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='primary'
|
||||
flush
|
||||
onClick={handleSave}
|
||||
disabled={updateMutation.isPending || isGenerating || !hasChanges}
|
||||
>
|
||||
{updateMutation.isPending ? 'Saving...' : 'Save'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
<ChipModalFooter
|
||||
onCancel={handleCloseAttempt}
|
||||
cancelDisabled={updateMutation.isPending || isGenerating}
|
||||
primaryAction={{
|
||||
label: updateMutation.isPending ? 'Saving...' : 'Save',
|
||||
onClick: handleSave,
|
||||
disabled: updateMutation.isPending || isGenerating || !hasChanges,
|
||||
}}
|
||||
/>
|
||||
</ChipModal>
|
||||
|
||||
<ChipModal
|
||||
<ChipConfirmModal
|
||||
open={showUnsavedChangesAlert}
|
||||
onOpenChange={setShowUnsavedChangesAlert}
|
||||
srTitle='Unsaved Changes'
|
||||
>
|
||||
<ChipModalHeader showDivider={false}>Unsaved Changes</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
You have unsaved changes. Are you sure you want to discard them?
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={() => setShowUnsavedChangesAlert(false)}>
|
||||
Keep Editing
|
||||
</Chip>
|
||||
<Chip variant='destructive' flush onClick={handleDiscardChanges}>
|
||||
Discard Changes
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
title='Unsaved Changes'
|
||||
description='You have unsaved changes. Are you sure you want to discard them?'
|
||||
dismissLabel='Keep editing'
|
||||
confirm={{
|
||||
label: 'Discard Changes',
|
||||
onClick: handleDiscardChanges,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
+29
-46
@@ -6,11 +6,7 @@ import {
|
||||
Button,
|
||||
ButtonGroup,
|
||||
ButtonGroupItem,
|
||||
Chip,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalFooter,
|
||||
ChipModalHeader,
|
||||
ChipConfirmModal,
|
||||
Expand,
|
||||
Label,
|
||||
Modal,
|
||||
@@ -278,10 +274,13 @@ export function GeneralDeploy({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ChipModal open={showLoadDialog} onOpenChange={setShowLoadDialog} srTitle='Load Deployment'>
|
||||
<ChipModalHeader showDivider={false}>Load Deployment</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
<ChipConfirmModal
|
||||
open={showLoadDialog}
|
||||
onOpenChange={setShowLoadDialog}
|
||||
srTitle='Load Deployment'
|
||||
title='Load Deployment'
|
||||
description={
|
||||
<>
|
||||
Are you sure you want to load{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>
|
||||
{versionToLoadInfo?.name || `v${versionToLoad?.version}`}
|
||||
@@ -290,31 +289,22 @@ export function GeneralDeploy({
|
||||
<span className='text-[var(--text-error)]'>
|
||||
This will replace your current workflow with the deployed version.
|
||||
</span>
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={() => setShowLoadDialog(false)}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='destructive'
|
||||
flush
|
||||
onClick={confirmLoadDeployment}
|
||||
disabled={revertMutation.isPending}
|
||||
>
|
||||
Load deployment
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
</>
|
||||
}
|
||||
confirm={{
|
||||
label: 'Load deployment',
|
||||
onClick: confirmLoadDeployment,
|
||||
pending: revertMutation.isPending,
|
||||
}}
|
||||
/>
|
||||
|
||||
<ChipModal
|
||||
<ChipConfirmModal
|
||||
open={showPromoteDialog}
|
||||
onOpenChange={setShowPromoteDialog}
|
||||
srTitle='Promote to live'
|
||||
>
|
||||
<ChipModalHeader showDivider={false}>Promote to live</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
title='Promote to live'
|
||||
description={
|
||||
<>
|
||||
Are you sure you want to promote{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>
|
||||
{versionToPromoteInfo?.name || `v${versionToPromote?.version}`}
|
||||
@@ -323,22 +313,15 @@ export function GeneralDeploy({
|
||||
<span className='text-[var(--text-primary)]'>
|
||||
This version will become the active deployment and serve all API requests.
|
||||
</span>
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={() => setShowPromoteDialog(false)}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='primary'
|
||||
flush
|
||||
onClick={confirmPromoteToLive}
|
||||
disabled={isPromotingVersion}
|
||||
>
|
||||
Promote to live
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
</>
|
||||
}
|
||||
confirm={{
|
||||
label: 'Promote to live',
|
||||
onClick: confirmPromoteToLive,
|
||||
variant: 'primary',
|
||||
pending: isPromotingVersion,
|
||||
}}
|
||||
/>
|
||||
|
||||
{workflowToShow && (
|
||||
<Modal open={showExpandedPreview} onOpenChange={setShowExpandedPreview}>
|
||||
|
||||
+27
-47
@@ -8,11 +8,7 @@ import { useParams } from 'next/navigation'
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
Chip,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalFooter,
|
||||
ChipModalHeader,
|
||||
ChipConfirmModal,
|
||||
Loader,
|
||||
Modal,
|
||||
ModalBody,
|
||||
@@ -833,45 +829,36 @@ export function DeployModal({
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
|
||||
<ChipModal
|
||||
<ChipConfirmModal
|
||||
open={Boolean(undeployTargetWorkflowId)}
|
||||
onOpenChange={(nextOpen) => {
|
||||
if (!nextOpen) setUndeployTargetWorkflowId(null)
|
||||
}}
|
||||
srTitle='Undeploy API'
|
||||
>
|
||||
<ChipModalHeader showDivider={false}>Undeploy API</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
title='Undeploy API'
|
||||
description={
|
||||
<>
|
||||
Are you sure you want to undeploy this workflow?{' '}
|
||||
<span className='text-[var(--text-error)]'>
|
||||
This will remove the API endpoint and make it unavailable to external users.
|
||||
</span>
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip
|
||||
variant='filled'
|
||||
flush
|
||||
onClick={() => setUndeployTargetWorkflowId(null)}
|
||||
disabled={isUndeploying}
|
||||
>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip variant='destructive' flush onClick={handleUndeploy} disabled={isUndeploying}>
|
||||
{isUndeploying ? 'Undeploying...' : 'Undeploy'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
</>
|
||||
}
|
||||
confirm={{
|
||||
label: 'Undeploy',
|
||||
onClick: handleUndeploy,
|
||||
pending: isUndeploying,
|
||||
pendingLabel: 'Undeploying...',
|
||||
}}
|
||||
/>
|
||||
|
||||
<ChipModal
|
||||
<ChipConfirmModal
|
||||
open={showA2aDeleteConfirm}
|
||||
onOpenChange={setShowA2aDeleteConfirm}
|
||||
srTitle='Delete A2A Agent'
|
||||
>
|
||||
<ChipModalHeader showDivider={false}>Delete A2A Agent</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
title='Delete A2A Agent'
|
||||
description={
|
||||
<>
|
||||
Are you sure you want to delete{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>
|
||||
{existingA2aAgent?.name || 'this agent'}
|
||||
@@ -881,22 +868,15 @@ export function DeployModal({
|
||||
This will permanently remove the agent configuration.
|
||||
</span>{' '}
|
||||
This action cannot be undone.
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip
|
||||
variant='filled'
|
||||
flush
|
||||
onClick={() => setShowA2aDeleteConfirm(false)}
|
||||
disabled={a2aSubmitting}
|
||||
>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip variant='destructive' flush onClick={handleA2aDelete} disabled={a2aSubmitting}>
|
||||
{a2aSubmitting ? 'Deleting...' : 'Delete'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
</>
|
||||
}
|
||||
confirm={{
|
||||
label: 'Delete',
|
||||
onClick: handleA2aDelete,
|
||||
pending: a2aSubmitting,
|
||||
pendingLabel: 'Deleting...',
|
||||
}}
|
||||
/>
|
||||
|
||||
<CreateApiKeyModal
|
||||
open={isCreateKeyModalOpen}
|
||||
|
||||
+53
-86
@@ -6,7 +6,7 @@ import { useParams } from 'next/navigation'
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
Chip,
|
||||
ChipConfirmModal,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalFooter,
|
||||
@@ -1144,112 +1144,79 @@ try {
|
||||
|
||||
{activeSection === 'schema' && (
|
||||
<ChipModalFooter
|
||||
leading={
|
||||
isEditing ? (
|
||||
<Chip variant='destructive' flush onClick={() => setShowDeleteConfirm(true)}>
|
||||
Delete
|
||||
</Chip>
|
||||
) : undefined
|
||||
onCancel={handleClose}
|
||||
secondaryAction={
|
||||
isEditing
|
||||
? {
|
||||
label: 'Delete',
|
||||
onClick: () => setShowDeleteConfirm(true),
|
||||
variant: 'destructive',
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<Chip variant='filled' flush onClick={handleClose}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='primary'
|
||||
flush
|
||||
onClick={() => setActiveSection('code')}
|
||||
disabled={!isSchemaValid || !!schemaError}
|
||||
>
|
||||
Next
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
primaryAction={{
|
||||
label: 'Next',
|
||||
onClick: () => setActiveSection('code'),
|
||||
disabled: !isSchemaValid || !!schemaError,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{activeSection === 'code' && (
|
||||
<ChipModalFooter
|
||||
leading={
|
||||
isEditing ? (
|
||||
<Chip variant='destructive' flush onClick={() => setShowDeleteConfirm(true)}>
|
||||
Delete
|
||||
</Chip>
|
||||
) : (
|
||||
<Chip variant='filled' flush onClick={() => setActiveSection('schema')}>
|
||||
Back
|
||||
</Chip>
|
||||
)
|
||||
onCancel={handleClose}
|
||||
secondaryAction={
|
||||
isEditing
|
||||
? {
|
||||
label: 'Delete',
|
||||
onClick: () => setShowDeleteConfirm(true),
|
||||
variant: 'destructive',
|
||||
}
|
||||
: { label: 'Back', onClick: () => setActiveSection('schema') }
|
||||
}
|
||||
>
|
||||
<Chip variant='filled' flush onClick={handleClose}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='primary'
|
||||
flush
|
||||
onClick={handleSave}
|
||||
disabled={!isSchemaValid || !!schemaError || !hasChanges}
|
||||
>
|
||||
{isEditing ? 'Update Tool' : 'Save Tool'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
primaryAction={{
|
||||
label: isEditing ? 'Update Tool' : 'Save Tool',
|
||||
onClick: handleSave,
|
||||
disabled: !isSchemaValid || !!schemaError || !hasChanges,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</ChipModal>
|
||||
|
||||
<ChipModal
|
||||
<ChipConfirmModal
|
||||
open={showDeleteConfirm}
|
||||
onOpenChange={setShowDeleteConfirm}
|
||||
srTitle='Delete Custom Tool'
|
||||
>
|
||||
<ChipModalHeader showDivider={false}>Delete Custom Tool</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
title='Delete Custom Tool'
|
||||
description={
|
||||
<>
|
||||
<span className='text-[var(--text-error)]'>
|
||||
This will permanently delete the tool and remove it from any workflows that are using
|
||||
it.
|
||||
</span>{' '}
|
||||
This action cannot be undone.
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip
|
||||
variant='filled'
|
||||
flush
|
||||
onClick={() => setShowDeleteConfirm(false)}
|
||||
disabled={deleteToolMutation.isPending}
|
||||
>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='destructive'
|
||||
flush
|
||||
onClick={handleDelete}
|
||||
disabled={deleteToolMutation.isPending}
|
||||
>
|
||||
{deleteToolMutation.isPending ? 'Deleting...' : 'Delete'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
</>
|
||||
}
|
||||
confirm={{
|
||||
label: 'Delete',
|
||||
onClick: handleDelete,
|
||||
pending: deleteToolMutation.isPending,
|
||||
pendingLabel: 'Deleting...',
|
||||
}}
|
||||
/>
|
||||
|
||||
<ChipModal
|
||||
<ChipConfirmModal
|
||||
open={showDiscardAlert}
|
||||
onOpenChange={setShowDiscardAlert}
|
||||
srTitle='Unsaved Changes'
|
||||
>
|
||||
<ChipModalHeader showDivider={false}>Unsaved Changes</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
You have unsaved changes. Are you sure you want to discard them?
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={() => setShowDiscardAlert(false)}>
|
||||
Keep Editing
|
||||
</Chip>
|
||||
<Chip variant='destructive' flush onClick={handleConfirmDiscard}>
|
||||
Discard Changes
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
title='Unsaved Changes'
|
||||
description='You have unsaved changes. Are you sure you want to discard them?'
|
||||
dismissLabel='Keep editing'
|
||||
confirm={{
|
||||
label: 'Discard Changes',
|
||||
onClick: handleConfirmDiscard,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -12,11 +12,7 @@ import {
|
||||
BubbleChatClose,
|
||||
BubbleChatPreview,
|
||||
Button,
|
||||
Chip,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalFooter,
|
||||
ChipModalHeader,
|
||||
ChipConfirmModal,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
@@ -931,14 +927,13 @@ export const Panel = memo(function Panel({ workspaceId: propWorkspaceId }: Panel
|
||||
</aside>
|
||||
|
||||
{/* Delete Confirmation Modal */}
|
||||
<ChipModal
|
||||
<ChipConfirmModal
|
||||
open={isDeleteModalOpen}
|
||||
onOpenChange={setIsDeleteModalOpen}
|
||||
srTitle='Delete Workflow'
|
||||
>
|
||||
<ChipModalHeader showDivider={false}>Delete Workflow</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
title='Delete Workflow'
|
||||
description={
|
||||
<>
|
||||
Are you sure you want to delete{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>
|
||||
{currentWorkflow?.name ?? 'this workflow'}
|
||||
@@ -948,22 +943,15 @@ export const Panel = memo(function Panel({ workspaceId: propWorkspaceId }: Panel
|
||||
All associated blocks, executions, and configuration will be removed.
|
||||
</span>{' '}
|
||||
You can restore it from Recently Deleted in Settings.
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip
|
||||
variant='filled'
|
||||
flush
|
||||
onClick={() => setIsDeleteModalOpen(false)}
|
||||
disabled={isDeleting}
|
||||
>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip variant='destructive' flush onClick={handleDeleteWorkflow} disabled={isDeleting}>
|
||||
{isDeleting ? 'Deleting...' : 'Delete'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
</>
|
||||
}
|
||||
confirm={{
|
||||
label: 'Delete',
|
||||
onClick: handleDeleteWorkflow,
|
||||
pending: isDeleting,
|
||||
pendingLabel: 'Deleting...',
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Floating Variables Modal */}
|
||||
<Variables readOnly={workflowLocked} />
|
||||
|
||||
+11
-21
@@ -10,7 +10,6 @@ import Image from 'next/image'
|
||||
import { Controller, useForm } from 'react-hook-form'
|
||||
import { z } from 'zod'
|
||||
import {
|
||||
Chip,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalField,
|
||||
@@ -255,6 +254,7 @@ export function HelpModal({ open, onOpenChange, workflowId, workspaceId }: HelpM
|
||||
<ChipModalHeader onClose={() => onOpenChange(false)}>Help & support</ChipModalHeader>
|
||||
|
||||
<form onSubmit={handleSubmit(onSubmit)} className='flex min-h-0 flex-1 flex-col'>
|
||||
<button type='submit' hidden disabled={helpMutation.isPending || isProcessing} />
|
||||
<ChipModalBody className='min-h-0'>
|
||||
<div
|
||||
ref={scrollContainerRef}
|
||||
@@ -348,31 +348,21 @@ export function HelpModal({ open, onOpenChange, workflowId, workspaceId }: HelpM
|
||||
</div>
|
||||
</ChipModalBody>
|
||||
|
||||
<ChipModalFooter>
|
||||
<Chip
|
||||
variant='filled'
|
||||
flush
|
||||
onClick={() => onOpenChange(false)}
|
||||
type='button'
|
||||
disabled={helpMutation.isPending}
|
||||
>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
type='submit'
|
||||
variant='primary'
|
||||
flush
|
||||
disabled={helpMutation.isPending || isProcessing}
|
||||
>
|
||||
{helpMutation.isPending
|
||||
<ChipModalFooter
|
||||
onCancel={() => onOpenChange(false)}
|
||||
cancelDisabled={helpMutation.isPending}
|
||||
primaryAction={{
|
||||
label: helpMutation.isPending
|
||||
? 'Submitting...'
|
||||
: submitStatus === 'error'
|
||||
? 'Error'
|
||||
: submitStatus === 'success'
|
||||
? 'Success'
|
||||
: 'Submit'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
: 'Submit',
|
||||
onClick: () => void handleSubmit(onSubmit)(),
|
||||
disabled: helpMutation.isPending || isProcessing,
|
||||
}}
|
||||
/>
|
||||
</form>
|
||||
</ChipModal>
|
||||
)
|
||||
|
||||
+10
-26
@@ -3,15 +3,7 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useParams, usePathname, useRouter } from 'next/navigation'
|
||||
import {
|
||||
ChevronDown,
|
||||
Chip,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalFooter,
|
||||
ChipModalHeader,
|
||||
chipVariants,
|
||||
} from '@/components/emcn'
|
||||
import { ChevronDown, ChipConfirmModal, chipVariants } from '@/components/emcn'
|
||||
import { useSession } from '@/lib/auth/auth-client'
|
||||
import { getSubscriptionAccessState } from '@/lib/billing/client'
|
||||
import { isEnterprise } from '@/lib/billing/plan-helpers'
|
||||
@@ -366,26 +358,18 @@ export function SettingsSidebar({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ChipModal
|
||||
<ChipConfirmModal
|
||||
open={showDiscardDialog}
|
||||
onOpenChange={(open) => !open && handleCancelDiscard()}
|
||||
srTitle='Unsaved changes'
|
||||
>
|
||||
<ChipModalHeader showDivider={false}>Unsaved changes</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
You have unsaved changes. Are you sure you want to discard them?
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={handleCancelDiscard}>
|
||||
Keep editing
|
||||
</Chip>
|
||||
<Chip variant='destructive' flush onClick={handleConfirmDiscard}>
|
||||
Discard changes
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
title='Unsaved changes'
|
||||
description='You have unsaved changes. Are you sure you want to discard them?'
|
||||
dismissLabel='Keep editing'
|
||||
confirm={{
|
||||
label: 'Discard changes',
|
||||
onClick: handleConfirmDiscard,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
+33
-37
@@ -1,14 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
Chip,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalField,
|
||||
ChipModalFooter,
|
||||
ChipModalHeader,
|
||||
} from '@/components/emcn'
|
||||
import { ChipConfirmModal, ChipModalField } from '@/components/emcn'
|
||||
|
||||
interface DeleteModalProps {
|
||||
/**
|
||||
@@ -252,38 +245,41 @@ export function DeleteModal({
|
||||
}
|
||||
|
||||
return (
|
||||
<ChipModal open={isOpen} onOpenChange={handleClose} srTitle={title}>
|
||||
<ChipModalHeader showDivider={false}>{title}</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
<ChipConfirmModal
|
||||
open={isOpen}
|
||||
onOpenChange={handleClose}
|
||||
srTitle={title}
|
||||
title={title}
|
||||
description={
|
||||
<>
|
||||
{renderDescription()}{' '}
|
||||
{restorableTypes.has(itemType)
|
||||
? 'You can restore it from Recently deleted in Settings.'
|
||||
: 'This action cannot be undone.'}
|
||||
</p>
|
||||
{isWorkspace && workspaceName && (
|
||||
<ChipModalField
|
||||
type='input'
|
||||
title={
|
||||
<>
|
||||
Type <span className='font-medium text-[var(--text-primary)]'>{workspaceName}</span>{' '}
|
||||
to confirm
|
||||
</>
|
||||
}
|
||||
value={confirmationText}
|
||||
onChange={setConfirmationText}
|
||||
placeholder={workspaceName}
|
||||
/>
|
||||
)}
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={handleClose} disabled={isDeleting}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip variant='destructive' flush onClick={onConfirm} disabled={isDeleting || !isConfirmed}>
|
||||
{isDeleting ? 'Deleting...' : 'Delete'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
</>
|
||||
}
|
||||
confirm={{
|
||||
label: 'Delete',
|
||||
onClick: onConfirm,
|
||||
pending: isDeleting,
|
||||
pendingLabel: 'Deleting...',
|
||||
disabled: !isConfirmed,
|
||||
}}
|
||||
>
|
||||
{isWorkspace && workspaceName && (
|
||||
<ChipModalField
|
||||
type='input'
|
||||
title={
|
||||
<>
|
||||
Type <span className='font-medium text-[var(--text-primary)]'>{workspaceName}</span>{' '}
|
||||
to confirm
|
||||
</>
|
||||
}
|
||||
value={confirmationText}
|
||||
onChange={setConfirmationText}
|
||||
placeholder={workspaceName}
|
||||
/>
|
||||
)}
|
||||
</ChipConfirmModal>
|
||||
)
|
||||
}
|
||||
|
||||
+9
-14
@@ -2,7 +2,6 @@
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import {
|
||||
Chip,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalError,
|
||||
@@ -65,19 +64,15 @@ export function CreateWorkspaceModal({
|
||||
/>
|
||||
<ChipModalError>{undefined}</ChipModalError>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={() => onOpenChange(false)} disabled={isCreating}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='primary'
|
||||
flush
|
||||
onClick={() => void handleSubmit()}
|
||||
disabled={!name.trim() || isCreating}
|
||||
>
|
||||
{isCreating ? 'Creating...' : 'Create'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
<ChipModalFooter
|
||||
onCancel={() => onOpenChange(false)}
|
||||
cancelDisabled={isCreating}
|
||||
primaryAction={{
|
||||
label: isCreating ? 'Creating...' : 'Create',
|
||||
onClick: () => void handleSubmit(),
|
||||
disabled: !name.trim() || isCreating,
|
||||
}}
|
||||
/>
|
||||
</ChipModal>
|
||||
)
|
||||
}
|
||||
|
||||
+9
-6
@@ -4,7 +4,6 @@ import { useCallback, useState } from 'react'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { useParams } from 'next/navigation'
|
||||
import {
|
||||
Chip,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalField,
|
||||
@@ -179,11 +178,15 @@ export function InviteModal({
|
||||
onChange={(role) => setInviteRole(role as PermissionType)}
|
||||
/>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='primary' onClick={handleSendInvites} disabled={isSendDisabled}>
|
||||
{isSubmitting ? 'Sending...' : 'Send invites'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
<ChipModalFooter
|
||||
onCancel={() => handleOpenChange(false)}
|
||||
cancelDisabled={isSubmitting}
|
||||
primaryAction={{
|
||||
label: isSubmitting ? 'Sending...' : 'Send invites',
|
||||
onClick: handleSendInvites,
|
||||
disabled: isSendDisabled,
|
||||
}}
|
||||
/>
|
||||
</ChipModal>
|
||||
)
|
||||
}
|
||||
|
||||
+14
-30
@@ -6,10 +6,7 @@ import { MoreHorizontal } from 'lucide-react'
|
||||
import {
|
||||
ChevronDown,
|
||||
Chip,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalFooter,
|
||||
ChipModalHeader,
|
||||
ChipConfirmModal,
|
||||
chipVariants,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@@ -684,39 +681,26 @@ function WorkspaceHeaderImpl({
|
||||
itemType='workspace'
|
||||
itemName={deleteTarget?.name}
|
||||
/>
|
||||
<ChipModal
|
||||
<ChipConfirmModal
|
||||
open={isLeaveModalOpen}
|
||||
onOpenChange={() => setIsLeaveModalOpen(false)}
|
||||
srTitle='Leave workspace'
|
||||
>
|
||||
<ChipModalHeader showDivider={false}>Leave workspace</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
title='Leave workspace'
|
||||
description={
|
||||
<>
|
||||
Are you sure you want to leave{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>{leaveTarget?.name}</span>? You
|
||||
will lose access to all workflows and data in this workspace. This action cannot be
|
||||
undone.
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip
|
||||
variant='filled'
|
||||
flush
|
||||
onClick={() => setIsLeaveModalOpen(false)}
|
||||
disabled={isLeavingWorkspace}
|
||||
>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='destructive'
|
||||
flush
|
||||
onClick={handleLeaveWorkspace}
|
||||
disabled={isLeavingWorkspace}
|
||||
>
|
||||
{isLeavingWorkspace ? 'Leaving...' : 'Leave workspace'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
</>
|
||||
}
|
||||
confirm={{
|
||||
label: 'Leave workspace',
|
||||
onClick: handleLeaveWorkspace,
|
||||
pending: isLeavingWorkspace,
|
||||
pendingLabel: 'Leaving...',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -90,7 +90,6 @@ import { useWorkspaceCredentials } from '@/hooks/queries/credentials'
|
||||
import { useFolderMap, useFolders } from '@/hooks/queries/folders'
|
||||
import { useKnowledgeBasesQuery } from '@/hooks/queries/kb/knowledge'
|
||||
import {
|
||||
useCreateMothershipChat,
|
||||
useDeleteMothershipChat,
|
||||
useDeleteMothershipChats,
|
||||
useMarkMothershipChatRead,
|
||||
@@ -109,7 +108,6 @@ import { useSettingsNavigation } from '@/hooks/use-settings-navigation'
|
||||
import { SIDEBAR_WIDTH } from '@/stores/constants'
|
||||
import { useFolderStore } from '@/stores/folders/store'
|
||||
import { useSearchModalStore } from '@/stores/modals/search/store'
|
||||
import { useMothershipDraftsStore } from '@/stores/mothership-drafts/store'
|
||||
import { useProvidersStore } from '@/stores/providers'
|
||||
import { useSidebarStore } from '@/stores/sidebar/store'
|
||||
|
||||
@@ -589,7 +587,6 @@ export const Sidebar = memo(function Sidebar() {
|
||||
}
|
||||
}, [activeNavItemHref])
|
||||
|
||||
const createChatMutation = useCreateMothershipChat(workspaceId)
|
||||
const deleteChatMutation = useDeleteMothershipChat(workspaceId)
|
||||
const deleteChatsMutation = useDeleteMothershipChats(workspaceId)
|
||||
const markChatReadMutation = useMarkMothershipChatRead(workspaceId)
|
||||
@@ -608,7 +605,6 @@ export const Sidebar = memo(function Sidebar() {
|
||||
preventDismiss: preventChatDismiss,
|
||||
} = useContextMenu()
|
||||
|
||||
const isCreatingChatRef = useRef(false)
|
||||
const contextMenuSelectionRef = useRef<{ chatIds: string[]; names: string[] }>({
|
||||
chatIds: [],
|
||||
names: [],
|
||||
@@ -1134,25 +1130,6 @@ export const Sidebar = memo(function Sidebar() {
|
||||
onSelect: handleCreateWorkflow,
|
||||
}
|
||||
|
||||
const handleNewChat = useCallback(async () => {
|
||||
if (!workspaceId || isCreatingChatRef.current) return
|
||||
isCreatingChatRef.current = true
|
||||
try {
|
||||
const { id } = await createChatMutation.mutateAsync()
|
||||
useMothershipDraftsStore.getState().clearDraft(`${workspaceId}:new`)
|
||||
navigateToPage(`/workspace/${workspaceId}/chat/${id}`)
|
||||
} catch {
|
||||
navigateToPage(`/workspace/${workspaceId}/home`)
|
||||
} finally {
|
||||
isCreatingChatRef.current = false
|
||||
}
|
||||
}, [workspaceId, navigateToPage])
|
||||
|
||||
const chatsPrimaryAction = {
|
||||
label: 'New chat',
|
||||
onSelect: handleNewChat,
|
||||
}
|
||||
|
||||
const handleSeeMoreChats = useCallback(() => setVisibleChatCount((prev) => prev + 5), [])
|
||||
const handleSeeLessChats = useCallback(() => setVisibleChatCount(5), [])
|
||||
|
||||
@@ -1241,12 +1218,6 @@ export const Sidebar = memo(function Sidebar() {
|
||||
handleCreateWorkflow()
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'add-task',
|
||||
handler: () => {
|
||||
handleNewChat()
|
||||
},
|
||||
},
|
||||
])
|
||||
)
|
||||
|
||||
@@ -1341,27 +1312,6 @@ export const Sidebar = memo(function Sidebar() {
|
||||
<div className='chats-section flex flex-shrink-0 flex-col'>
|
||||
<div className='flex h-[18px] flex-shrink-0 items-center justify-between px-4'>
|
||||
<div className='text-[var(--text-muted)] text-small'>Chats</div>
|
||||
{!isCollapsed && (
|
||||
<div className='flex items-center justify-center gap-2'>
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
<Button
|
||||
variant='quiet'
|
||||
className='h-[18px] w-[18px] rounded-sm p-0'
|
||||
onClick={handleNewChat}
|
||||
disabled={createChatMutation.isPending}
|
||||
>
|
||||
<Plus className='h-[16px] w-[16px]' />
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content>
|
||||
<Tooltip.Shortcut keys={isMac ? '⌘⇧K' : 'Ctrl+Shift+K'}>
|
||||
New chat
|
||||
</Tooltip.Shortcut>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{isCollapsed ? (
|
||||
<CollapsedSidebarMenu
|
||||
@@ -1369,7 +1319,6 @@ export const Sidebar = memo(function Sidebar() {
|
||||
hover={chatsHover}
|
||||
ariaLabel='Chats'
|
||||
className='mt-2'
|
||||
primaryAction={chatsPrimaryAction}
|
||||
>
|
||||
{chatsLoading ? (
|
||||
<DropdownMenuItem disabled>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { LandingPromptStorage } from '@/lib/core/utils/browser-storage'
|
||||
import { getCanonicalBlocksByCategory } from '@/blocks/registry'
|
||||
import type { BlockIcon } from '@/blocks/types'
|
||||
|
||||
@@ -82,6 +83,40 @@ export function getIntegrationMatcher(): IntegrationMatcher {
|
||||
return cachedMatcher
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrites bare integration names in `text` to `@`-mention form (`Slack` →
|
||||
* `@Slack`) so they chip when the prompt is populated into the chat input —
|
||||
* the auto-mention pipeline deliberately ignores un-prefixed names (mention
|
||||
* treatment is strictly opt-in via a token-starting `@`), so curated prompts
|
||||
* that should chip must opt in here. Idempotent: names already prefixed with
|
||||
* `@` are left untouched.
|
||||
*/
|
||||
export function mentionifyIntegrations(text: string): string {
|
||||
const { regex } = getIntegrationMatcher()
|
||||
if (!regex || !text) return text
|
||||
return text.replace(regex, (match: string, _name: string, offset: number) =>
|
||||
offset > 0 && text[offset - 1] === '@' ? match : `@${match}`
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a CURATED prompt (a suggested action, template, or showcase CTA — never
|
||||
* free-form user prose) for the home chat input to consume after navigation,
|
||||
* running it through {@link mentionifyIntegrations} first so its integration
|
||||
* names chip with brand icons on arrival.
|
||||
*
|
||||
* This is the single seam every curated-prompt producer that hands off via
|
||||
* {@link LandingPromptStorage} must use — it pairs the rewrite with the store so
|
||||
* a new producer cannot forget the rewrite (the regression class this guards
|
||||
* against). User-typed prose (e.g. the landing preview panel) intentionally
|
||||
* bypasses this and calls {@link LandingPromptStorage.store} directly, since
|
||||
* bare integration names in prose must never be auto-chipped (the scunthorpe
|
||||
* problem).
|
||||
*/
|
||||
export function storeCuratedPrompt(prompt: string): boolean {
|
||||
return LandingPromptStorage.store(mentionifyIntegrations(prompt))
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazily builds (once per session) and returns all known integrations sorted
|
||||
* alphabetically by display name for menu rendering. Shares the underlying
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
* / `ModalFooter` — drop your controls in as children.
|
||||
*
|
||||
* Body items are declared via the polymorphic `ChipModalField`. Each field
|
||||
* picks a `type` (`'input'`, `'email'`, `'textarea'`, `'dropdown'`, or
|
||||
* `'custom'`) and the field owns all chrome internally — consumers describe
|
||||
* intent, never styling. Custom is the escape hatch for arbitrary content
|
||||
* (e.g. an `InfoCard`, a `TagInput`).
|
||||
* picks a `type` (`'input'`, `'email'`, `'textarea'`, `'dropdown'`, `'file'`,
|
||||
* `'emails'`, or `'custom'`) and the field owns all chrome internally —
|
||||
* consumers describe intent, never styling. Custom is the escape hatch for
|
||||
* arbitrary content (e.g. an `InfoCard`, a `TagInput`).
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
@@ -28,9 +28,10 @@
|
||||
* <TagInput items={items} onAdd={add} onRemove={remove} variant='block' />
|
||||
* </ChipModalField>
|
||||
* </ChipModalBody>
|
||||
* <ChipModalFooter>
|
||||
* <Chip variant='primary' onClick={send}>Send invites</Chip>
|
||||
* </ChipModalFooter>
|
||||
* <ChipModalFooter
|
||||
* onCancel={() => setOpen(false)}
|
||||
* primaryAction={{ label: 'Send invites', onClick: send }}
|
||||
* />
|
||||
* </ChipModal>
|
||||
* ```
|
||||
*/
|
||||
@@ -40,6 +41,7 @@
|
||||
import * as React from 'react'
|
||||
import { X } from 'lucide-react'
|
||||
import { Button } from '@/components/emcn/components/button/button'
|
||||
import { Chip, type ChipProps } from '@/components/emcn/components/chip/chip'
|
||||
import {
|
||||
ChipDropdown,
|
||||
type ChipDropdownOption,
|
||||
@@ -124,34 +126,19 @@ ChipModal.displayName = 'ChipModal'
|
||||
export interface ChipModalHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
/** Optional leading icon. Pass `null`/omit for a title-only header. */
|
||||
icon?: React.ComponentType<{ className?: string }> | null
|
||||
/** When provided, renders a trailing close button. */
|
||||
onClose?: () => void
|
||||
/** Invoked when the trailing close button is activated. Always rendered. */
|
||||
onClose: () => void
|
||||
/** Accessible label for the close button. */
|
||||
closeAriaLabel?: string
|
||||
/**
|
||||
* Whether to render the divider line below the header title.
|
||||
* Set to `false` for compact confirmations (destructive / disconnect flows)
|
||||
* where the body is plain prose rather than labeled fields.
|
||||
* @default true
|
||||
*/
|
||||
showDivider?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Header row with optional leading icon, title, and optional trailing close.
|
||||
* Renders an inset divider below the title to match the panel's rhythm.
|
||||
* Header row with optional leading icon, title, and a trailing close button.
|
||||
* Always renders an inset divider below the title to match the panel's rhythm.
|
||||
*/
|
||||
const ChipModalHeader = React.forwardRef<HTMLDivElement, ChipModalHeaderProps>(
|
||||
(
|
||||
{
|
||||
className,
|
||||
children,
|
||||
icon: Icon = null,
|
||||
onClose,
|
||||
closeAriaLabel = 'Close',
|
||||
showDivider = true,
|
||||
...props
|
||||
},
|
||||
{ className, children, icon: Icon = null, onClose, closeAriaLabel = 'Close', ...props },
|
||||
ref
|
||||
) => (
|
||||
<div ref={ref} className={cn('flex flex-col', className)} {...props}>
|
||||
@@ -160,19 +147,17 @@ const ChipModalHeader = React.forwardRef<HTMLDivElement, ChipModalHeaderProps>(
|
||||
{Icon ? <Icon className='size-[12px] flex-shrink-0 text-[var(--text-icon)]' /> : null}
|
||||
<span className='min-w-0 truncate text-[var(--text-body)] text-sm'>{children}</span>
|
||||
</div>
|
||||
{onClose ? (
|
||||
<Button
|
||||
type='button'
|
||||
variant='ghost'
|
||||
onClick={onClose}
|
||||
className='relative size-[14px] flex-shrink-0 p-0 before:absolute before:inset-[-14px] before:content-[""]'
|
||||
>
|
||||
<X className='size-[14px] text-[var(--text-icon)]' />
|
||||
<span className='sr-only'>{closeAriaLabel}</span>
|
||||
</Button>
|
||||
) : null}
|
||||
<Button
|
||||
type='button'
|
||||
variant='ghost'
|
||||
onClick={onClose}
|
||||
className='relative size-[14px] flex-shrink-0 p-0 before:absolute before:inset-[-14px] before:content-[""]'
|
||||
>
|
||||
<X className='size-[14px] text-[var(--text-icon)]' />
|
||||
<span className='sr-only'>{closeAriaLabel}</span>
|
||||
</Button>
|
||||
</div>
|
||||
{showDivider && <ChipModalSeparator className='mt-3' />}
|
||||
<ChipModalSeparator className='mt-3' />
|
||||
</div>
|
||||
)
|
||||
)
|
||||
@@ -751,42 +736,274 @@ function ChipModalFileControl({
|
||||
)
|
||||
}
|
||||
|
||||
export interface ChipModalFooterProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
/**
|
||||
* A single footer action button. Rendered internally as a {@link Chip} so every
|
||||
* modal footer stays visually identical — callers describe intent (label,
|
||||
* handler, optional variant), never JSX or chrome. Encode pending state in the
|
||||
* `label` and `disabled` (e.g. `saving ? 'Saving...' : 'Save'`).
|
||||
*/
|
||||
export interface ChipModalFooterAction {
|
||||
/** Button label. */
|
||||
label: React.ReactNode
|
||||
/** Click handler. */
|
||||
onClick: () => void
|
||||
/** Disables the button. */
|
||||
disabled?: boolean
|
||||
/**
|
||||
* Optional leading slot rendered on the left side of the footer — use for
|
||||
* a destructive secondary action (e.g. a Delete button in an edit flow).
|
||||
* When provided, the footer switches to `justify-between` automatically.
|
||||
* Chip variant, restricted to the three footer-appropriate options so a
|
||||
* footer can never drift from the design system.
|
||||
* @default 'primary' for `primaryAction`, 'filled' for `secondaryAction`
|
||||
*/
|
||||
leading?: React.ReactNode
|
||||
variant?: Extract<ChipProps['variant'], 'primary' | 'destructive' | 'filled'>
|
||||
}
|
||||
|
||||
export interface ChipModalFooterProps {
|
||||
/**
|
||||
* Dismiss handler for the always-present Cancel button. Like the header's
|
||||
* close (X), Cancel is structural — it always reads "Cancel" and there is no
|
||||
* prop to relabel or remove it. Its enabled state can be controlled via
|
||||
* {@link ChipModalFooterProps.cancelDisabled}.
|
||||
*/
|
||||
onCancel: () => void
|
||||
/**
|
||||
* Disables the Cancel button. Set this while a primary/secondary action is
|
||||
* in flight (e.g. an async delete or save) so the user cannot dismiss the
|
||||
* modal and assume the operation was aborted while the mutation keeps running.
|
||||
* @default false
|
||||
*/
|
||||
cancelDisabled?: boolean
|
||||
/** Primary action, anchored bottom-right (e.g. Save, Create, Delete). */
|
||||
primaryAction: ChipModalFooterAction
|
||||
/**
|
||||
* Optional auxiliary action docked to the far-left, opposite the
|
||||
* Cancel/primary cluster — e.g. Delete in an edit flow or Back in a wizard.
|
||||
*/
|
||||
secondaryAction?: ChipModalFooterAction
|
||||
}
|
||||
|
||||
/**
|
||||
* Footer row. Renders the leading inset separator and a tinted action bar.
|
||||
* Pass `leading` to left-dock a secondary action (e.g. Delete in edit mode);
|
||||
* primary actions always go in `children` and are right-aligned.
|
||||
* Shared footer chrome — the inset separator plus the tinted `--surface-3` bar
|
||||
* with the standard gutter. Single source of truth so {@link ChipModalFooter}
|
||||
* and {@link ChipConfirmModal} render an identical footer surface. `leftSlot`
|
||||
* docks to the far-left (opposite the right-anchored button cluster); when
|
||||
* omitted the cluster is right-justified.
|
||||
*/
|
||||
const ChipModalFooter = React.forwardRef<HTMLDivElement, ChipModalFooterProps>(
|
||||
({ className, leading, children, ...props }, ref) => (
|
||||
function ChipModalFooterShell({
|
||||
leftSlot,
|
||||
children,
|
||||
}: {
|
||||
leftSlot?: React.ReactNode
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<div className='flex flex-col'>
|
||||
<ChipModalSeparator />
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'flex items-center gap-2 bg-[var(--surface-3)] px-4 pt-2 pb-2',
|
||||
leading ? 'justify-between' : 'justify-end',
|
||||
className
|
||||
leftSlot ? 'justify-between' : 'justify-end'
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{leading && <div>{leading}</div>}
|
||||
{leftSlot ?? null}
|
||||
<div className='flex gap-2'>{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Footer row with a fixed, declarative shape: an optional far-left
|
||||
* `secondaryAction`, then the always-present Cancel and the right-anchored
|
||||
* `primaryAction`. Buttons are described via {@link ChipModalFooterAction} and
|
||||
* rendered as {@link Chip}s, so no footer can drift from the canonical layout.
|
||||
*
|
||||
* For "are you sure?" confirmations, reach for {@link ChipConfirmModal} instead
|
||||
* — a confirmation's dismiss button is a named decision ("Keep editing"), not
|
||||
* the structural Cancel this footer guarantees.
|
||||
*/
|
||||
function ChipModalFooter({
|
||||
onCancel,
|
||||
cancelDisabled,
|
||||
primaryAction,
|
||||
secondaryAction,
|
||||
}: ChipModalFooterProps) {
|
||||
return (
|
||||
<ChipModalFooterShell
|
||||
leftSlot={
|
||||
secondaryAction ? (
|
||||
<Chip
|
||||
variant={secondaryAction.variant ?? 'filled'}
|
||||
flush
|
||||
onClick={secondaryAction.onClick}
|
||||
disabled={secondaryAction.disabled}
|
||||
>
|
||||
{secondaryAction.label}
|
||||
</Chip>
|
||||
) : undefined
|
||||
}
|
||||
>
|
||||
<Chip variant='filled' flush onClick={onCancel} disabled={cancelDisabled}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant={primaryAction.variant ?? 'primary'}
|
||||
flush
|
||||
onClick={primaryAction.onClick}
|
||||
disabled={primaryAction.disabled}
|
||||
>
|
||||
{primaryAction.label}
|
||||
</Chip>
|
||||
</ChipModalFooterShell>
|
||||
)
|
||||
}
|
||||
|
||||
ChipModalFooter.displayName = 'ChipModalFooter'
|
||||
|
||||
/**
|
||||
* The confirming action of a {@link ChipConfirmModal}. Unlike a
|
||||
* {@link ChipModalFooterAction}, pending state is first-class: set `pending`
|
||||
* while the async action runs and the primitive disables BOTH buttons (so the
|
||||
* dismiss can't be clicked mid-mutation) and swaps in `pendingLabel`.
|
||||
*/
|
||||
export interface ChipConfirmAction {
|
||||
/** Resting button label (e.g. `'Delete'`). */
|
||||
label: string
|
||||
/** Invoked when the user confirms. */
|
||||
onClick: () => void
|
||||
/**
|
||||
* Chip variant. Confirmations are usually destructive, so this defaults to
|
||||
* `'destructive'`; use `'primary'` for a non-destructive confirm (e.g.
|
||||
* "Promote to live").
|
||||
* @default 'destructive'
|
||||
*/
|
||||
variant?: Extract<ChipProps['variant'], 'primary' | 'destructive' | 'filled'>
|
||||
/**
|
||||
* Marks the action in-flight: disables both the confirm and dismiss buttons
|
||||
* and, when {@link ChipConfirmAction.pendingLabel} is set, shows it in place
|
||||
* of `label`.
|
||||
*/
|
||||
pending?: boolean
|
||||
/** Label shown while `pending` (e.g. `'Deleting...'`). Falls back to `label`. */
|
||||
pendingLabel?: string
|
||||
/** Additional disable condition independent of `pending` (e.g. an unmet "type to confirm"). */
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export interface ChipConfirmModalProps {
|
||||
/** Controlled open state. */
|
||||
open: boolean
|
||||
/**
|
||||
* Open-state change handler and the SINGLE dismiss path — the header close
|
||||
* (X), the dismiss button, Escape, and overlay click all route through
|
||||
* `onOpenChange(false)`. Put any teardown (clearing the targeted row, etc.)
|
||||
* here so no dismiss path can skip it.
|
||||
*/
|
||||
onOpenChange: (open: boolean) => void
|
||||
/** Title rendered in the header. */
|
||||
title: React.ReactNode
|
||||
/** Optional leading header icon. */
|
||||
icon?: React.ComponentType<{ className?: string }> | null
|
||||
/**
|
||||
* Confirmation copy. Rendered with the standard secondary body styling, so
|
||||
* pass a plain string or inline `<>…<span className='…text-primary…'>name</span>…</>`
|
||||
* for emphasis — no wrapping `<p>` needed.
|
||||
*/
|
||||
description?: React.ReactNode
|
||||
/**
|
||||
* Extra body content below `description` — e.g. a "type the name to confirm"
|
||||
* {@link ChipModalField}. Most confirmations omit this.
|
||||
*/
|
||||
children?: React.ReactNode
|
||||
/** The confirming action (Delete / Discard / Remove …). */
|
||||
confirm: ChipConfirmAction
|
||||
/**
|
||||
* Label for the dismiss button. In a confirmation the dismiss button is a
|
||||
* named decision, so this is honest API (unlike a form footer's structural
|
||||
* Cancel). Defaults to `'Cancel'`; pass `'Keep editing'` for unsaved-changes.
|
||||
* @default 'Cancel'
|
||||
*/
|
||||
dismissLabel?: string
|
||||
/**
|
||||
* Panel width. Confirmations are compact, so defaults to `'sm'`.
|
||||
* @default 'sm'
|
||||
*/
|
||||
size?: ChipModalProps['size']
|
||||
/** Screen-reader title; defaults to the string form of `title` when omitted. */
|
||||
srTitle?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Compact "are you sure?" confirmation dialog. Models the confirmation button
|
||||
* grammar directly — a named dismiss decision plus a (usually destructive)
|
||||
* confirm — instead of bending the form footer's structural Cancel to fit.
|
||||
*
|
||||
* The primitive owns the safety rails that every hand-rolled confirm modal had
|
||||
* to remember: a single dismiss path shared by the header X / dismiss button /
|
||||
* Escape (so teardown can't desync), and disabling dismiss while the confirm is
|
||||
* in flight. Drop richer body content (a "type to confirm" field) in as
|
||||
* `children`.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <ChipConfirmModal
|
||||
* open={open}
|
||||
* onOpenChange={(next) => { if (!next) setTarget(null); setOpen(next) }}
|
||||
* title='Delete API key'
|
||||
* description={<>Deleting <strong>{target?.name}</strong> revokes access immediately.</>}
|
||||
* confirm={{ label: 'Delete', onClick: handleDelete, pending: isDeleting, pendingLabel: 'Deleting...' }}
|
||||
* />
|
||||
* ```
|
||||
*/
|
||||
function ChipConfirmModal({
|
||||
open,
|
||||
onOpenChange,
|
||||
title,
|
||||
icon,
|
||||
description,
|
||||
children,
|
||||
confirm,
|
||||
dismissLabel = 'Cancel',
|
||||
size = 'sm',
|
||||
srTitle,
|
||||
}: ChipConfirmModalProps) {
|
||||
const dismiss = React.useCallback(() => onOpenChange(false), [onOpenChange])
|
||||
const confirmLabel = confirm.pending ? (confirm.pendingLabel ?? confirm.label) : confirm.label
|
||||
|
||||
return (
|
||||
<ChipModal
|
||||
open={open}
|
||||
onOpenChange={onOpenChange}
|
||||
size={size}
|
||||
srTitle={srTitle ?? (typeof title === 'string' ? title : 'Confirm')}
|
||||
>
|
||||
<ChipModalHeader icon={icon} onClose={dismiss}>
|
||||
{title}
|
||||
</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
{description ? (
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>{description}</p>
|
||||
) : null}
|
||||
{children}
|
||||
</ChipModalBody>
|
||||
<ChipModalFooterShell>
|
||||
<Chip variant='filled' flush onClick={dismiss} disabled={confirm.pending}>
|
||||
{dismissLabel}
|
||||
</Chip>
|
||||
<Chip
|
||||
variant={confirm.variant ?? 'destructive'}
|
||||
flush
|
||||
onClick={confirm.onClick}
|
||||
disabled={confirm.disabled || confirm.pending}
|
||||
>
|
||||
{confirmLabel}
|
||||
</Chip>
|
||||
</ChipModalFooterShell>
|
||||
</ChipModal>
|
||||
)
|
||||
}
|
||||
|
||||
ChipConfirmModal.displayName = 'ChipConfirmModal'
|
||||
|
||||
export interface ChipModalErrorProps extends React.HTMLAttributes<HTMLParagraphElement> {
|
||||
/** Error message. When falsy the component renders nothing. */
|
||||
children?: React.ReactNode
|
||||
@@ -824,6 +1041,7 @@ const ChipModalError = React.forwardRef<HTMLParagraphElement, ChipModalErrorProp
|
||||
ChipModalError.displayName = 'ChipModalError'
|
||||
|
||||
export {
|
||||
ChipConfirmModal,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
ChipModalError,
|
||||
|
||||
@@ -32,6 +32,9 @@ export {
|
||||
} from './chip-dropdown/chip-dropdown'
|
||||
export { ChipInput, type ChipInputProps } from './chip-input/chip-input'
|
||||
export {
|
||||
type ChipConfirmAction,
|
||||
ChipConfirmModal,
|
||||
type ChipConfirmModalProps,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
type ChipModalDropdownOption,
|
||||
@@ -41,6 +44,8 @@ export {
|
||||
ChipModalField,
|
||||
type ChipModalFieldProps,
|
||||
ChipModalFooter,
|
||||
type ChipModalFooterAction,
|
||||
type ChipModalFooterProps,
|
||||
ChipModalHeader,
|
||||
type ChipModalHeaderProps,
|
||||
type ChipModalProps,
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
AvatarImage,
|
||||
Checkbox,
|
||||
Chip,
|
||||
ChipConfirmModal,
|
||||
ChipInput,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
@@ -209,26 +210,17 @@ function AddMembersModal({
|
||||
)}
|
||||
<ChipModalError>{errorMessage}</ChipModalError>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip
|
||||
variant='filled'
|
||||
flush
|
||||
onClick={() => {
|
||||
setSearchTerm('')
|
||||
onOpenChange(false)
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='primary'
|
||||
flush
|
||||
onClick={onAddMembers}
|
||||
disabled={selectedMemberIds.size === 0 || isAdding}
|
||||
>
|
||||
{isAdding ? 'Adding...' : 'Add Members'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
<ChipModalFooter
|
||||
onCancel={() => {
|
||||
setSearchTerm('')
|
||||
onOpenChange(false)
|
||||
}}
|
||||
primaryAction={{
|
||||
label: isAdding ? 'Adding...' : 'Add Members',
|
||||
onClick: onAddMembers,
|
||||
disabled: selectedMemberIds.size === 0 || isAdding,
|
||||
}}
|
||||
/>
|
||||
</ChipModal>
|
||||
)
|
||||
}
|
||||
@@ -797,6 +789,31 @@ export function AccessControl() {
|
||||
}
|
||||
}, [viewingGroup, editingConfig, workspaceId, updatePermissionGroup])
|
||||
|
||||
const handleCloseConfigModal = useCallback(() => {
|
||||
if (hasConfigChanges) {
|
||||
setShowUnsavedChanges(true)
|
||||
} else {
|
||||
setShowConfigModal(false)
|
||||
setProviderSearchTerm('')
|
||||
setIntegrationSearchTerm('')
|
||||
setPlatformSearchTerm('')
|
||||
}
|
||||
}, [hasConfigChanges])
|
||||
|
||||
const handleDiscardConfig = useCallback(() => {
|
||||
setShowUnsavedChanges(false)
|
||||
setShowConfigModal(false)
|
||||
setEditingConfig(null)
|
||||
setProviderSearchTerm('')
|
||||
setIntegrationSearchTerm('')
|
||||
setPlatformSearchTerm('')
|
||||
}, [])
|
||||
|
||||
const handleSaveConfigFromUnsaved = useCallback(() => {
|
||||
setShowUnsavedChanges(false)
|
||||
handleSaveConfig()
|
||||
}, [handleSaveConfig])
|
||||
|
||||
const handleOpenAddMembersModal = useCallback(() => {
|
||||
setSelectedMemberIds(new Set())
|
||||
setAddMembersError(null)
|
||||
@@ -1375,32 +1392,14 @@ export function AccessControl() {
|
||||
</div>
|
||||
)}
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip
|
||||
variant='filled'
|
||||
flush
|
||||
onClick={() => {
|
||||
if (hasConfigChanges) {
|
||||
setShowUnsavedChanges(true)
|
||||
} else {
|
||||
setShowConfigModal(false)
|
||||
setProviderSearchTerm('')
|
||||
setIntegrationSearchTerm('')
|
||||
setPlatformSearchTerm('')
|
||||
}
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='primary'
|
||||
flush
|
||||
onClick={handleSaveConfig}
|
||||
disabled={updatePermissionGroup.isPending || !hasConfigChanges}
|
||||
>
|
||||
{updatePermissionGroup.isPending ? 'Saving...' : 'Save'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
<ChipModalFooter
|
||||
onCancel={handleCloseConfigModal}
|
||||
primaryAction={{
|
||||
label: updatePermissionGroup.isPending ? 'Saving...' : 'Save',
|
||||
onClick: handleSaveConfig,
|
||||
disabled: updatePermissionGroup.isPending || !hasConfigChanges,
|
||||
}}
|
||||
/>
|
||||
</ChipModal>
|
||||
|
||||
<ChipModal
|
||||
@@ -1409,39 +1408,27 @@ export function AccessControl() {
|
||||
size='sm'
|
||||
srTitle='Unsaved Changes'
|
||||
>
|
||||
<ChipModalHeader showDivider={false}>Unsaved Changes</ChipModalHeader>
|
||||
<ChipModalHeader onClose={() => setShowUnsavedChanges(false)}>
|
||||
Unsaved Changes
|
||||
</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
You have unsaved changes. Do you want to save them before closing?
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip
|
||||
variant='destructive'
|
||||
flush
|
||||
onClick={() => {
|
||||
setShowUnsavedChanges(false)
|
||||
setShowConfigModal(false)
|
||||
setEditingConfig(null)
|
||||
setProviderSearchTerm('')
|
||||
setIntegrationSearchTerm('')
|
||||
setPlatformSearchTerm('')
|
||||
}}
|
||||
>
|
||||
Discard Changes
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='primary'
|
||||
flush
|
||||
onClick={() => {
|
||||
setShowUnsavedChanges(false)
|
||||
handleSaveConfig()
|
||||
}}
|
||||
disabled={updatePermissionGroup.isPending}
|
||||
>
|
||||
{updatePermissionGroup.isPending ? 'Saving...' : 'Save Changes'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
<ChipModalFooter
|
||||
onCancel={() => setShowUnsavedChanges(false)}
|
||||
secondaryAction={{
|
||||
label: 'Discard Changes',
|
||||
onClick: handleDiscardConfig,
|
||||
variant: 'destructive',
|
||||
}}
|
||||
primaryAction={{
|
||||
label: updatePermissionGroup.isPending ? 'Saving...' : 'Save Changes',
|
||||
onClick: handleSaveConfigFromUnsaved,
|
||||
disabled: updatePermissionGroup.isPending,
|
||||
}}
|
||||
/>
|
||||
</ChipModal>
|
||||
|
||||
<AddMembersModal
|
||||
@@ -1562,52 +1549,38 @@ export function AccessControl() {
|
||||
</ChipModalField>
|
||||
<ChipModalError>{createError}</ChipModalError>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={handleCloseCreateModal}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='primary'
|
||||
flush
|
||||
onClick={handleCreatePermissionGroup}
|
||||
disabled={!newGroupName.trim() || createPermissionGroup.isPending}
|
||||
>
|
||||
{createPermissionGroup.isPending ? 'Creating...' : 'Create'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
<ChipModalFooter
|
||||
onCancel={handleCloseCreateModal}
|
||||
primaryAction={{
|
||||
label: createPermissionGroup.isPending ? 'Creating...' : 'Create',
|
||||
onClick: handleCreatePermissionGroup,
|
||||
disabled: !newGroupName.trim() || createPermissionGroup.isPending,
|
||||
}}
|
||||
/>
|
||||
</ChipModal>
|
||||
|
||||
<ChipModal
|
||||
<ChipConfirmModal
|
||||
open={!!deletingGroup}
|
||||
onOpenChange={() => setDeletingGroup(null)}
|
||||
size='sm'
|
||||
srTitle='Delete Permission Group'
|
||||
>
|
||||
<ChipModalHeader showDivider={false}>Delete Permission Group</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
title='Delete Permission Group'
|
||||
description={
|
||||
<>
|
||||
Are you sure you want to delete{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>{deletingGroup?.name}</span>?{' '}
|
||||
<span className='text-[var(--text-error)]'>
|
||||
All members will be removed from this group.
|
||||
</span>{' '}
|
||||
This action cannot be undone.
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={() => setDeletingGroup(null)}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='destructive'
|
||||
flush
|
||||
onClick={confirmDelete}
|
||||
disabled={deletePermissionGroup.isPending}
|
||||
>
|
||||
{deletePermissionGroup.isPending ? 'Deleting...' : 'Delete'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
</>
|
||||
}
|
||||
confirm={{
|
||||
label: 'Delete',
|
||||
onClick: confirmDelete,
|
||||
pending: deletePermissionGroup.isPending,
|
||||
pendingLabel: 'Deleting...',
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
Button,
|
||||
Callout,
|
||||
Chip,
|
||||
ChipConfirmModal,
|
||||
ChipInput,
|
||||
ChipModal,
|
||||
ChipModalBody,
|
||||
@@ -326,38 +327,25 @@ function DrainRow({ drain, organizationId, expanded, onToggleExpand }: DrainRowP
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
<ChipModal
|
||||
<ChipConfirmModal
|
||||
open={showDeleteConfirm}
|
||||
onOpenChange={setShowDeleteConfirm}
|
||||
srTitle='Delete Drain'
|
||||
>
|
||||
<ChipModalHeader showDivider={false}>Delete Drain</ChipModalHeader>
|
||||
<ChipModalBody>
|
||||
<p className='px-2 text-[var(--text-secondary)] text-sm'>
|
||||
title='Delete Drain'
|
||||
description={
|
||||
<>
|
||||
Are you sure you want to delete{' '}
|
||||
<span className='font-medium text-[var(--text-primary)]'>{drain.name}</span>? This
|
||||
action cannot be undone.
|
||||
</p>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip
|
||||
variant='filled'
|
||||
flush
|
||||
onClick={() => setShowDeleteConfirm(false)}
|
||||
disabled={deleteMutation.isPending}
|
||||
>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='destructive'
|
||||
flush
|
||||
onClick={handleConfirmDelete}
|
||||
disabled={deleteMutation.isPending}
|
||||
>
|
||||
{deleteMutation.isPending ? 'Deleting...' : 'Delete'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
</ChipModal>
|
||||
</>
|
||||
}
|
||||
confirm={{
|
||||
label: 'Delete',
|
||||
onClick: handleConfirmDelete,
|
||||
pending: deleteMutation.isPending,
|
||||
pendingLabel: 'Deleting...',
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -501,19 +489,15 @@ function CreateDrainModal({ organizationId, onClose }: CreateDrainModalProps) {
|
||||
<spec.FormFields state={destState} setState={setDestState} />
|
||||
</section>
|
||||
</ChipModalBody>
|
||||
<ChipModalFooter>
|
||||
<Chip variant='filled' flush onClick={onClose} disabled={createMutation.isPending}>
|
||||
Cancel
|
||||
</Chip>
|
||||
<Chip
|
||||
variant='primary'
|
||||
flush
|
||||
onClick={handleSubmit}
|
||||
disabled={!canSubmit || createMutation.isPending}
|
||||
>
|
||||
{createMutation.isPending ? 'Creating...' : 'Create drain'}
|
||||
</Chip>
|
||||
</ChipModalFooter>
|
||||
<ChipModalFooter
|
||||
onCancel={onClose}
|
||||
cancelDisabled={createMutation.isPending}
|
||||
primaryAction={{
|
||||
label: createMutation.isPending ? 'Creating...' : 'Create drain',
|
||||
onClick: handleSubmit,
|
||||
disabled: !canSubmit || createMutation.isPending,
|
||||
}}
|
||||
/>
|
||||
</ChipModal>
|
||||
)
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user