@burdenoff/microfe-channel 2026.624.2 → 2026.625.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/channel/modules/channels/pages/ChannelDashboardPage.js +108 -98
- package/dist/channel/modules/channels/pages/ChannelDashboardPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelDetailsPage.js +289 -279
- package/dist/channel/modules/channels/pages/ChannelDetailsPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelSettingsPage.js +140 -131
- package/dist/channel/modules/channels/pages/ChannelSettingsPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelUsersPage.js +170 -173
- package/dist/channel/modules/channels/pages/ChannelUsersPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelsListPage.js +127 -120
- package/dist/channel/modules/channels/pages/ChannelsListPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ConnectionDetailsPage.js +161 -155
- package/dist/channel/modules/channels/pages/ConnectionDetailsPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ConnectionsListPage.js +70 -68
- package/dist/channel/modules/channels/pages/ConnectionsListPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/CreateChannelPage.js +27 -24
- package/dist/channel/modules/channels/pages/CreateChannelPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/CreateConnectionPage.js +161 -156
- package/dist/channel/modules/channels/pages/CreateConnectionPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/DeliveryHistoryPage.js +179 -182
- package/dist/channel/modules/channels/pages/DeliveryHistoryPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/EditChannelPage.js +130 -122
- package/dist/channel/modules/channels/pages/EditChannelPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/EditConnectionPage.js +137 -132
- package/dist/channel/modules/channels/pages/EditConnectionPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/SendMessagePage.js +87 -84
- package/dist/channel/modules/channels/pages/SendMessagePage.js.map +1 -1
- package/dist/channel/modules/channels/pages/WebhookEventsPage.js +193 -194
- package/dist/channel/modules/channels/pages/WebhookEventsPage.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EditChannelPage.js","names":[],"sources":["../../../../../src/channel/modules/channels/pages/EditChannelPage.tsx"],"sourcesContent":["/**\n * Edit Channel Page\n *\n * Allows editing an existing channel's configuration.\n * Uses the same form structure as CreateChannelPage but pre-fills existing values.\n */\n\nimport { useState, useEffect, useRef } from 'react';\nimport { useNavigate, useParams } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n ArrowLeft,\n Save,\n Loader2,\n AlertCircle,\n Mail,\n Smartphone,\n Bell,\n Inbox,\n MessageSquare,\n Hash,\n Users,\n MessageCircle,\n Send,\n Trash2,\n} from 'lucide-react';\nimport { useChannel, useChannels } from '../../../hooks/useChannels';\nimport {\n type ChannelPlatform,\n type AdapterType,\n type ChannelStatus,\n type UpdateChannelInput,\n} from '../../../hooks/useChannels';\nimport { nativeImpact, nativeNotify } from '../../../../utils/nativeBridge';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\n\n// ============================================================================\n// PLATFORM CONFIG (same as CreateChannelPage)\n// ============================================================================\n\nconst PLATFORM_OPTIONS: {\n value: ChannelPlatform;\n label: string;\n description: string;\n icon: typeof Mail;\n color: string;\n adapters: { value: AdapterType; label: string }[];\n}[] = [\n {\n value: 'EMAIL',\n label: 'Email',\n description: 'Send emails via various providers',\n icon: Mail,\n color: 'text-status-info-text bg-status-info-bg-subtle border-status-info-border',\n adapters: [\n { value: 'AWS_SES', label: 'AWS SES' },\n { value: 'MAILGUN', label: 'Mailgun' },\n { value: 'POSTMARK', label: 'Postmark' },\n { value: 'MANDRILL', label: 'Mandrill' },\n { value: 'RESEND', label: 'Resend' },\n { value: 'AZURE_ACS', label: 'Azure Communication Services' },\n { value: 'SMTP', label: 'Custom SMTP' },\n ],\n },\n {\n value: 'SMS',\n label: 'SMS',\n description: 'Send text messages',\n icon: Smartphone,\n color: 'text-status-success-text bg-status-success-bg-subtle border-status-success-border/30',\n adapters: [\n { value: 'TWILIO_SMS', label: 'Twilio' },\n { value: 'AWS_SNS', label: 'AWS SNS' },\n { value: 'VONAGE', label: 'Vonage' },\n { value: 'PLIVO', label: 'Plivo' },\n { value: 'MESSAGEBIRD', label: 'MessageBird' },\n { value: 'INFOBIP', label: 'Infobip' },\n ],\n },\n {\n value: 'PUSH',\n label: 'Push',\n description: 'Mobile & web push',\n icon: Bell,\n color: 'text-accent-purple bg-accent-purple-subtle border-accent-purple/30',\n adapters: [\n { value: 'FCM', label: 'Firebase Cloud Messaging' },\n { value: 'APNS', label: 'Apple Push Notification Service' },\n { value: 'ONESIGNAL', label: 'OneSignal' },\n { value: 'EXPO', label: 'Expo Push' },\n ],\n },\n {\n value: 'INAPP',\n label: 'In-App',\n description: 'Internal in-app notifications',\n icon: Inbox,\n color: 'text-status-warning-text bg-status-warning-bg-subtle border-status-warning-border/30',\n adapters: [{ value: 'INAPP_NATIVE', label: 'Native In-App' }],\n },\n {\n value: 'WHATSAPP',\n label: 'WhatsApp',\n description: 'WhatsApp Business messaging',\n icon: MessageSquare,\n color: 'text-status-success-text bg-status-success-bg-subtle border-status-success-border/30',\n adapters: [\n { value: 'WHATSAPP_CLOUD', label: 'WhatsApp Cloud API' },\n { value: 'WHATSAPP_TWILIO', label: 'WhatsApp via Twilio' },\n ],\n },\n {\n value: 'SLACK',\n label: 'Slack',\n description: 'Slack bot & channels',\n icon: Hash,\n color: 'text-accent-purple bg-accent-purple-subtle border-accent-purple/30',\n adapters: [{ value: 'SLACK_BOT', label: 'Slack Bot' }],\n },\n {\n value: 'TEAMS',\n label: 'Teams',\n description: 'Microsoft Teams',\n icon: Users,\n color: 'text-status-info-text bg-status-info-bg-subtle border-status-info-border',\n adapters: [{ value: 'TEAMS_BOT', label: 'Teams Bot' }],\n },\n {\n value: 'DISCORD',\n label: 'Discord',\n description: 'Discord bots & webhooks',\n icon: MessageCircle,\n color: 'text-accent-indigo bg-accent-indigo-subtle border-accent-indigo/30',\n adapters: [{ value: 'DISCORD_BOT', label: 'Discord Bot' }],\n },\n {\n value: 'WEBCHAT',\n label: 'Webchat',\n description: 'Website live chat',\n icon: MessageSquare,\n color: 'text-accent-teal bg-accent-teal-subtle border-accent-teal/30',\n adapters: [{ value: 'WEBCHAT_NATIVE', label: 'Native Webchat' }],\n },\n {\n value: 'WEBHOOK',\n label: 'Webhook',\n description: 'Generic HTTP webhook',\n icon: Send,\n color: 'text-text-secondary bg-bg-sunken/50 border-border-subtle',\n adapters: [{ value: 'WEBHOOK_HTTP', label: 'HTTP Webhook' }],\n },\n];\n\nconst PRIORITY_OPTIONS = [\n { value: 0, label: '0 - Lowest' },\n { value: 1, label: '1 - Low' },\n { value: 2, label: '2 - Normal' },\n { value: 3, label: '3 - High' },\n { value: 4, label: '4 - Highest' },\n];\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\nexport function EditChannelPage() {\n const navigate = useNavigate();\n const { channelId } = useParams<{ channelId: string }>();\n const { channel, isLoading, error } = useChannel(channelId!);\n const { updateChannel, deleteChannel } = useChannels();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n // Form state\n const [name, setName] = useState('');\n const [description, setDescription] = useState('');\n const [adapterType, setAdapterType] = useState<AdapterType>('AWS_SES');\n const [priority, setPriority] = useState(2);\n const [status, setStatus] = useState<ChannelStatus>('INACTIVE');\n\n // UI state\n const [isSaving, setIsSaving] = useState(false);\n const [isDeleting, setIsDeleting] = useState(false);\n const [saveError, setSaveError] = useState<string | null>(null);\n const [deleteConfirm, setDeleteConfirm] = useState(false);\n\n // Load channel data into form — guard against re-initialization and defer\n // the state update to a microtask so setState does not run synchronously\n // inside the effect body (react-hooks/set-state-in-effect).\n const loadedChannelIdRef = useRef<string | null>(null);\n useEffect(() => {\n if (channel && channel.id !== loadedChannelIdRef.current) {\n loadedChannelIdRef.current = channel.id;\n void Promise.resolve().then(() => {\n setName(channel.name || '');\n setDescription(channel.description || '');\n setAdapterType((channel.adapterType || 'AWS_SES') as AdapterType);\n setPriority(channel.priority || 2);\n setStatus((channel.status || 'INACTIVE') as ChannelStatus);\n });\n }\n }, [channel]);\n\n // Update adapter options when platform changes\n const currentPlatform = (channel?.platform || 'EMAIL') as ChannelPlatform;\n const selectedPlatform = PLATFORM_OPTIONS.find((p) => p.value === currentPlatform);\n\n const handleSave = async () => {\n if (!channelId) return;\n\n if (!name.trim()) {\n setSaveError(tr('channel.validation.nameRequired', 'Channel name is required'));\n return;\n }\n\n setIsSaving(true);\n setSaveError(null);\n\n try {\n const input: UpdateChannelInput = {\n name: name.trim(),\n description: description.trim() || undefined,\n adapterType,\n priority,\n status,\n };\n\n const result = await updateChannel(channelId, input);\n if (result) {\n navigate(`/channels/${channelId}`);\n } else {\n setSaveError(tr('channel.updateFailed', 'Failed to update channel'));\n }\n } catch (err: unknown) {\n const message =\n err instanceof Error ? err.message : tr('channel.updateFailed', 'Failed to update channel');\n setSaveError(message);\n } finally {\n setIsSaving(false);\n }\n };\n\n const handleDelete = async () => {\n if (!channelId || !deleteConfirm) return;\n void nativeImpact('medium');\n\n setIsDeleting(true);\n try {\n const success = await deleteChannel(channelId);\n if (success) {\n void nativeNotify('success');\n navigate('../list');\n } else {\n void nativeNotify('error');\n }\n } catch (err: unknown) {\n void nativeNotify('error');\n const message =\n err instanceof Error ? err.message : tr('channel.deleteFailed', 'Failed to delete channel');\n setSaveError(message);\n setIsDeleting(false);\n }\n };\n\n if (isLoading) {\n return (\n <div className=\"flex items-center justify-center min-h-screen\">\n <Loader2 className=\"size-8 animate-spin text-text-secondary\" />\n </div>\n );\n }\n\n if (error || !channel) {\n return (\n <div className=\"flex flex-col items-center justify-center min-h-screen gap-4\">\n <AlertCircle className=\"size-12 text-status-error-text\" />\n <h1 className=\"text-xl font-semibold\">\n {tr('channel.channelNotFound', 'Channel not found')}\n </h1>\n <button\n type=\"button\"\n onClick={() => navigate('../list')}\n className=\"text-primary hover:underline\"\n >\n {tr('channel.backToChannels', 'Back to channels')}\n </button>\n </div>\n );\n }\n\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_UPDATE}>\n <div className=\"min-h-screen bg-bg-surface\">\n {/* Header */}\n <div className=\"border-b bg-bg-surface\">\n <div className=\"container mx-auto px-6 py-4\">\n <div className=\"flex items-center gap-4\">\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${channelId}`)}\n className=\"p-2 hover:bg-bg-sunken rounded-lg transition-colors\"\n >\n <ArrowLeft className=\"size-5 \" />\n </button>\n <div>\n <h1 className=\"text-2xl font-bold\">{tr('channel.editChannel', 'Edit Channel')}</h1>\n <p className=\"text-sm text-text-secondary\">\n {tr('channel.updateChannelConfig', 'Update channel configuration')}\n </p>\n </div>\n </div>\n </div>\n </div>\n\n {/* Form */}\n <div className=\"container mx-auto px-6 py-8 max-w-2xl\">\n <div className=\"space-y-6\">\n {/* Error Alert */}\n {saveError && (\n <div className=\"p-4 rounded-lg bg-status-error-bg/10 border border-status-error-border/20 flex items-center gap-3\">\n <AlertCircle className=\"size-5 text-status-error-text shrink-0\" />\n <p className=\"text-sm text-status-error-text\">{saveError}</p>\n </div>\n )}\n\n {/* Channel Info (read-only) */}\n <div className=\"p-4 rounded-lg bg-bg-sunken/50 border space-y-2\">\n <div className=\"flex items-center gap-2\">\n {selectedPlatform && (\n <>\n {(() => {\n const Icon = selectedPlatform.icon;\n return <Icon className=\"size-5 text-text-secondary\" />;\n })()}\n <span className=\"font-medium\">{currentPlatform}</span>\n </>\n )}\n </div>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'channel.platformLockedHint',\n 'Platform cannot be changed after channel creation. Create a new channel to use a different platform.'\n )}\n </p>\n </div>\n\n {/* Name */}\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">\n {tr('channel.channelName', 'Channel Name')} *\n </label>\n <input\n aria-label={tr(\n 'channel.editNamePlaceholder',\n 'e.g., Production Email, Customer SMS'\n )}\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder={tr(\n 'channel.editNamePlaceholder',\n 'e.g., Production Email, Customer SMS'\n )}\n className=\"w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary\"\n />\n </div>\n\n {/* Description */}\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">\n {tr('channel.description', 'Description')}\n </label>\n <textarea\n aria-label={tr(\n 'channel.editDescriptionPlaceholder',\n 'Optional description for this channel'\n )}\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n placeholder={tr(\n 'channel.editDescriptionPlaceholder',\n 'Optional description for this channel'\n )}\n rows={3}\n className=\"w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary resize-none\"\n />\n </div>\n\n {/* Adapter Type */}\n {selectedPlatform && (\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">\n {tr('channel.adapterProvider', 'Adapter / Provider')}\n </label>\n <select\n value={adapterType}\n onChange={(e) => setAdapterType(e.target.value as AdapterType)}\n className=\"w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n {selectedPlatform.adapters.map((adapter) => (\n <option key={adapter.value} value={adapter.value}>\n {adapter.label}\n </option>\n ))}\n </select>\n </div>\n )}\n\n {/* Priority & Status */}\n <div className=\"grid grid-cols-2 gap-4\">\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">{tr('channel.priority', 'Priority')}</label>\n <select\n value={priority}\n onChange={(e) => setPriority(Number(e.target.value))}\n className=\"w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n {PRIORITY_OPTIONS.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n <p className=\"text-xs text-text-secondary\">\n {tr(\n 'channel.priorityFailoverHint',\n 'Higher priority channels are used first in failover'\n )}\n </p>\n </div>\n\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">{tr('channel.status', 'Status')}</label>\n <select\n value={status}\n onChange={(e) => setStatus(e.target.value as ChannelStatus)}\n className=\"w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value=\"INACTIVE\">{tr('channel.status.inactive', 'Inactive')}</option>\n <option value=\"ACTIVE\">{tr('channel.status.active', 'Active')}</option>\n <option value=\"SUSPENDED\">{tr('channel.status.suspended', 'Suspended')}</option>\n </select>\n </div>\n </div>\n\n {/* Actions */}\n <div className=\"flex items-center justify-between pt-6 border-t\">\n {/* Delete */}\n <div>\n {!deleteConfirm ? (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_DELETE}>\n <button\n type=\"button\"\n onClick={() => setDeleteConfirm(true)}\n className=\"px-4 py-2 text-sm text-status-error-text hover:bg-status-error-bg/10 rounded-lg transition-colors flex items-center gap-2\"\n >\n <Trash2 className=\"size-4 \" />\n {tr('channel.deleteChannel', 'Delete Channel')}\n </button>\n </PermissionGuard>\n ) : (\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm text-text-secondary\">\n {tr('channel.confirmDelete', 'Confirm delete?')}\n </span>\n <button\n type=\"button\"\n onClick={handleDelete}\n disabled={isDeleting}\n className=\"px-4 py-2 text-sm bg-status-error-bg text-status-error-text rounded-lg hover:bg-status-error-bg/90 transition-colors flex items-center gap-2\"\n >\n {isDeleting ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Trash2 className=\"size-4 \" />\n )}\n {tr('channel.delete', 'Delete')}\n </button>\n <button\n type=\"button\"\n onClick={() => setDeleteConfirm(false)}\n className=\"px-4 py-2 text-sm text-text-secondary hover:bg-bg-sunken rounded-lg transition-colors\"\n >\n {tr('channel.cancel', 'Cancel')}\n </button>\n </div>\n )}\n </div>\n\n {/* Save */}\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${channelId}`)}\n className=\"px-4 py-2 text-sm text-text-secondary hover:bg-bg-sunken rounded-lg transition-colors\"\n >\n {tr('channel.cancel', 'Cancel')}\n </button>\n <button\n type=\"button\"\n onClick={handleSave}\n disabled={isSaving || !name.trim()}\n className=\"px-6 py-2 text-sm bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg/90 transition-colors flex items-center gap-2 disabled:opacity-50\"\n >\n {isSaving ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Save className=\"size-4 \" />\n )}\n {tr('channel.saveChanges', 'Save Changes')}\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EditChannelPage.js","names":[],"sources":["../../../../../src/channel/modules/channels/pages/EditChannelPage.tsx"],"sourcesContent":["/**\n * Edit Channel Page\n *\n * Allows editing an existing channel's configuration.\n * Uses the same form structure as CreateChannelPage but pre-fills existing values.\n */\n\nimport { useState, useEffect, useRef } from 'react';\nimport { useNavigate, useParams } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n ArrowLeft,\n Save,\n Loader2,\n AlertCircle,\n Mail,\n Smartphone,\n Bell,\n Inbox,\n MessageSquare,\n Hash,\n Users,\n MessageCircle,\n Send,\n Trash2,\n} from 'lucide-react';\nimport { useChannel, useChannels } from '../../../hooks/useChannels';\nimport {\n type ChannelPlatform,\n type AdapterType,\n type ChannelStatus,\n type UpdateChannelInput,\n} from '../../../hooks/useChannels';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { nativeImpact, nativeNotify } from '../../../../utils/nativeBridge';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\n\n// ============================================================================\n// PLATFORM CONFIG (same as CreateChannelPage)\n// ============================================================================\n\nconst PLATFORM_OPTIONS: {\n value: ChannelPlatform;\n label: string;\n description: string;\n icon: typeof Mail;\n color: string;\n adapters: { value: AdapterType; label: string }[];\n}[] = [\n {\n value: 'EMAIL',\n label: 'Email',\n description: 'Send emails via various providers',\n icon: Mail,\n color: 'text-status-info-text bg-status-info-bg-subtle border-status-info-border',\n adapters: [\n { value: 'AWS_SES', label: 'AWS SES' },\n { value: 'MAILGUN', label: 'Mailgun' },\n { value: 'POSTMARK', label: 'Postmark' },\n { value: 'MANDRILL', label: 'Mandrill' },\n { value: 'RESEND', label: 'Resend' },\n { value: 'AZURE_ACS', label: 'Azure Communication Services' },\n { value: 'SMTP', label: 'Custom SMTP' },\n ],\n },\n {\n value: 'SMS',\n label: 'SMS',\n description: 'Send text messages',\n icon: Smartphone,\n color: 'text-status-success-text bg-status-success-bg-subtle border-status-success-border/30',\n adapters: [\n { value: 'TWILIO_SMS', label: 'Twilio' },\n { value: 'AWS_SNS', label: 'AWS SNS' },\n { value: 'VONAGE', label: 'Vonage' },\n { value: 'PLIVO', label: 'Plivo' },\n { value: 'MESSAGEBIRD', label: 'MessageBird' },\n { value: 'INFOBIP', label: 'Infobip' },\n ],\n },\n {\n value: 'PUSH',\n label: 'Push',\n description: 'Mobile & web push',\n icon: Bell,\n color: 'text-accent-purple bg-accent-purple-subtle border-accent-purple/30',\n adapters: [\n { value: 'FCM', label: 'Firebase Cloud Messaging' },\n { value: 'APNS', label: 'Apple Push Notification Service' },\n { value: 'ONESIGNAL', label: 'OneSignal' },\n { value: 'EXPO', label: 'Expo Push' },\n ],\n },\n {\n value: 'INAPP',\n label: 'In-App',\n description: 'Internal in-app notifications',\n icon: Inbox,\n color: 'text-status-warning-text bg-status-warning-bg-subtle border-status-warning-border/30',\n adapters: [{ value: 'INAPP_NATIVE', label: 'Native In-App' }],\n },\n {\n value: 'WHATSAPP',\n label: 'WhatsApp',\n description: 'WhatsApp Business messaging',\n icon: MessageSquare,\n color: 'text-status-success-text bg-status-success-bg-subtle border-status-success-border/30',\n adapters: [\n { value: 'WHATSAPP_CLOUD', label: 'WhatsApp Cloud API' },\n { value: 'WHATSAPP_TWILIO', label: 'WhatsApp via Twilio' },\n ],\n },\n {\n value: 'SLACK',\n label: 'Slack',\n description: 'Slack bot & channels',\n icon: Hash,\n color: 'text-accent-purple bg-accent-purple-subtle border-accent-purple/30',\n adapters: [{ value: 'SLACK_BOT', label: 'Slack Bot' }],\n },\n {\n value: 'TEAMS',\n label: 'Teams',\n description: 'Microsoft Teams',\n icon: Users,\n color: 'text-status-info-text bg-status-info-bg-subtle border-status-info-border',\n adapters: [{ value: 'TEAMS_BOT', label: 'Teams Bot' }],\n },\n {\n value: 'DISCORD',\n label: 'Discord',\n description: 'Discord bots & webhooks',\n icon: MessageCircle,\n color: 'text-accent-indigo bg-accent-indigo-subtle border-accent-indigo/30',\n adapters: [{ value: 'DISCORD_BOT', label: 'Discord Bot' }],\n },\n {\n value: 'WEBCHAT',\n label: 'Webchat',\n description: 'Website live chat',\n icon: MessageSquare,\n color: 'text-accent-teal bg-accent-teal-subtle border-accent-teal/30',\n adapters: [{ value: 'WEBCHAT_NATIVE', label: 'Native Webchat' }],\n },\n {\n value: 'WEBHOOK',\n label: 'Webhook',\n description: 'Generic HTTP webhook',\n icon: Send,\n color: 'text-text-secondary bg-bg-sunken/50 border-border-subtle',\n adapters: [{ value: 'WEBHOOK_HTTP', label: 'HTTP Webhook' }],\n },\n];\n\nconst PRIORITY_OPTIONS = [\n { value: 0, label: '0 - Lowest' },\n { value: 1, label: '1 - Low' },\n { value: 2, label: '2 - Normal' },\n { value: 3, label: '3 - High' },\n { value: 4, label: '4 - Highest' },\n];\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\nexport function EditChannelPage() {\n const navigate = useNavigate();\n const { channelId } = useParams<{ channelId: string }>();\n const { channel, isLoading, error } = useChannel(channelId!);\n const { updateChannel, deleteChannel } = useChannels();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n // Form state\n const [name, setName] = useState('');\n const [description, setDescription] = useState('');\n const [adapterType, setAdapterType] = useState<AdapterType>('AWS_SES');\n const [priority, setPriority] = useState(2);\n const [status, setStatus] = useState<ChannelStatus>('INACTIVE');\n\n // UI state\n const [isSaving, setIsSaving] = useState(false);\n const [isDeleting, setIsDeleting] = useState(false);\n const [saveError, setSaveError] = useState<string | null>(null);\n const [deleteConfirm, setDeleteConfirm] = useState(false);\n\n // Load channel data into form — guard against re-initialization and defer\n // the state update to a microtask so setState does not run synchronously\n // inside the effect body (react-hooks/set-state-in-effect).\n const loadedChannelIdRef = useRef<string | null>(null);\n useEffect(() => {\n if (channel && channel.id !== loadedChannelIdRef.current) {\n loadedChannelIdRef.current = channel.id;\n void Promise.resolve().then(() => {\n setName(channel.name || '');\n setDescription(channel.description || '');\n setAdapterType((channel.adapterType || 'AWS_SES') as AdapterType);\n setPriority(channel.priority || 2);\n setStatus((channel.status || 'INACTIVE') as ChannelStatus);\n });\n }\n }, [channel]);\n\n // Update adapter options when platform changes\n const currentPlatform = (channel?.platform || 'EMAIL') as ChannelPlatform;\n const selectedPlatform = PLATFORM_OPTIONS.find((p) => p.value === currentPlatform);\n\n const handleSave = async () => {\n if (!channelId) return;\n\n if (!name.trim()) {\n setSaveError(tr('channel.validation.nameRequired', 'Channel name is required'));\n return;\n }\n\n setIsSaving(true);\n setSaveError(null);\n\n try {\n const input: UpdateChannelInput = {\n name: name.trim(),\n description: description.trim() || undefined,\n adapterType,\n priority,\n status,\n };\n\n const result = await updateChannel(channelId, input);\n if (result) {\n navigate(`/channels/${channelId}`);\n } else {\n setSaveError(tr('channel.updateFailed', 'Failed to update channel'));\n }\n } catch (err: unknown) {\n const message =\n err instanceof Error ? err.message : tr('channel.updateFailed', 'Failed to update channel');\n setSaveError(message);\n } finally {\n setIsSaving(false);\n }\n };\n\n const handleDelete = async () => {\n if (!channelId || !deleteConfirm) return;\n void nativeImpact('medium');\n\n setIsDeleting(true);\n try {\n const success = await deleteChannel(channelId);\n if (success) {\n void nativeNotify('success');\n navigate('../list');\n } else {\n void nativeNotify('error');\n }\n } catch (err: unknown) {\n void nativeNotify('error');\n const message =\n err instanceof Error ? err.message : tr('channel.deleteFailed', 'Failed to delete channel');\n setSaveError(message);\n setIsDeleting(false);\n }\n };\n\n if (isLoading) {\n return (\n <div className=\"flex items-center justify-center min-h-screen\">\n <Loader2 className=\"size-8 animate-spin text-text-secondary\" />\n </div>\n );\n }\n\n if (error || !channel) {\n return (\n <div className=\"flex flex-col items-center justify-center min-h-screen gap-4\">\n <AlertCircle className=\"size-12 text-status-error-text\" />\n <h1 className=\"text-xl font-semibold\">\n {tr('channel.channelNotFound', 'Channel not found')}\n </h1>\n <button\n type=\"button\"\n onClick={() => navigate('../list')}\n className=\"text-primary hover:underline\"\n >\n {tr('channel.backToChannels', 'Back to channels')}\n </button>\n </div>\n );\n }\n\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_UPDATE}>\n <div className=\"min-h-screen bg-bg-surface\">\n {/* Header */}\n <div className=\"border-b bg-bg-surface\">\n <div className=\"container mx-auto px-6 py-4\">\n <div className=\"flex items-center gap-4\">\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${channelId}`)}\n className=\"p-2 hover:bg-bg-sunken rounded-lg transition-colors\"\n >\n <ArrowLeft className=\"size-5 \" />\n </button>\n <div>\n <h1 className=\"text-2xl font-bold\">{tr('channel.editChannel', 'Edit Channel')}</h1>\n <p className=\"text-sm text-text-secondary\">\n {tr('channel.updateChannelConfig', 'Update channel configuration')}\n </p>\n </div>\n </div>\n </div>\n </div>\n\n {/* Form */}\n <div className=\"container mx-auto px-6 py-8 max-w-2xl\">\n <div className=\"space-y-6\">\n {/* Error Alert */}\n {saveError && (\n <div className=\"p-4 rounded-lg bg-status-error-bg/10 border border-status-error-border/20 flex items-center gap-3\">\n <AlertCircle className=\"size-5 text-status-error-text shrink-0\" />\n <p className=\"text-sm text-status-error-text\">{saveError}</p>\n </div>\n )}\n\n {/* Channel Info (read-only) */}\n <GlassCard treatment=\"insight\" className=\"p-4 border-border-seam space-y-2\">\n <div className=\"flex items-center gap-2\">\n {selectedPlatform && (\n <>\n {(() => {\n const Icon = selectedPlatform.icon;\n return <Icon className=\"size-5 text-text-secondary\" />;\n })()}\n <span className=\"font-medium\">{currentPlatform}</span>\n </>\n )}\n </div>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'channel.platformLockedHint',\n 'Platform cannot be changed after channel creation. Create a new channel to use a different platform.'\n )}\n </p>\n </GlassCard>\n\n {/* Channel fields */}\n <GlassCard treatment=\"plain\" className=\"p-5 border-border-seam space-y-6\">\n {/* Name */}\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">\n {tr('channel.channelName', 'Channel Name')} *\n </label>\n <input\n aria-label={tr(\n 'channel.editNamePlaceholder',\n 'e.g., Production Email, Customer SMS'\n )}\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder={tr(\n 'channel.editNamePlaceholder',\n 'e.g., Production Email, Customer SMS'\n )}\n className=\"w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary\"\n />\n </div>\n\n {/* Description */}\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">\n {tr('channel.description', 'Description')}\n </label>\n <textarea\n aria-label={tr(\n 'channel.editDescriptionPlaceholder',\n 'Optional description for this channel'\n )}\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n placeholder={tr(\n 'channel.editDescriptionPlaceholder',\n 'Optional description for this channel'\n )}\n rows={3}\n className=\"w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary resize-none\"\n />\n </div>\n\n {/* Adapter Type */}\n {selectedPlatform && (\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">\n {tr('channel.adapterProvider', 'Adapter / Provider')}\n </label>\n <select\n value={adapterType}\n onChange={(e) => setAdapterType(e.target.value as AdapterType)}\n className=\"w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n {selectedPlatform.adapters.map((adapter) => (\n <option key={adapter.value} value={adapter.value}>\n {adapter.label}\n </option>\n ))}\n </select>\n </div>\n )}\n\n {/* Priority & Status */}\n <div className=\"grid grid-cols-2 gap-4\">\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">{tr('channel.priority', 'Priority')}</label>\n <select\n value={priority}\n onChange={(e) => setPriority(Number(e.target.value))}\n className=\"w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n {PRIORITY_OPTIONS.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n <p className=\"text-xs text-text-secondary\">\n {tr(\n 'channel.priorityFailoverHint',\n 'Higher priority channels are used first in failover'\n )}\n </p>\n </div>\n\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">{tr('channel.status', 'Status')}</label>\n <select\n value={status}\n onChange={(e) => setStatus(e.target.value as ChannelStatus)}\n className=\"w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value=\"INACTIVE\">{tr('channel.status.inactive', 'Inactive')}</option>\n <option value=\"ACTIVE\">{tr('channel.status.active', 'Active')}</option>\n <option value=\"SUSPENDED\">{tr('channel.status.suspended', 'Suspended')}</option>\n </select>\n </div>\n </div>\n </GlassCard>\n\n {/* Actions */}\n <div className=\"flex items-center justify-between pt-6 border-t\">\n {/* Delete */}\n <div>\n {!deleteConfirm ? (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_DELETE}>\n <button\n type=\"button\"\n onClick={() => setDeleteConfirm(true)}\n className=\"px-4 py-2 text-sm text-status-error-text hover:bg-status-error-bg/10 rounded-lg transition-colors flex items-center gap-2\"\n >\n <Trash2 className=\"size-4 \" />\n {tr('channel.deleteChannel', 'Delete Channel')}\n </button>\n </PermissionGuard>\n ) : (\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm text-text-secondary\">\n {tr('channel.confirmDelete', 'Confirm delete?')}\n </span>\n <button\n type=\"button\"\n onClick={handleDelete}\n disabled={isDeleting}\n className=\"px-4 py-2 text-sm bg-status-error-bg text-status-error-text rounded-lg hover:bg-status-error-bg/90 transition-colors flex items-center gap-2\"\n >\n {isDeleting ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Trash2 className=\"size-4 \" />\n )}\n {tr('channel.delete', 'Delete')}\n </button>\n <button\n type=\"button\"\n onClick={() => setDeleteConfirm(false)}\n className=\"px-4 py-2 text-sm text-text-secondary hover:bg-bg-sunken rounded-lg transition-colors\"\n >\n {tr('channel.cancel', 'Cancel')}\n </button>\n </div>\n )}\n </div>\n\n {/* Save */}\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${channelId}`)}\n className=\"px-4 py-2 text-sm text-text-secondary hover:bg-bg-sunken rounded-lg transition-colors\"\n >\n {tr('channel.cancel', 'Cancel')}\n </button>\n <button\n type=\"button\"\n onClick={handleSave}\n disabled={isSaving || !name.trim()}\n className=\"px-6 py-2 text-sm bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bg/90 transition-colors flex items-center gap-2 disabled:opacity-50\"\n >\n {isSaving ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Save className=\"size-4 \" />\n )}\n {tr('channel.saveChanges', 'Save Changes')}\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
|
|
@@ -9,181 +9,186 @@ import { useI18n as u } from "@burdenoff/fe-libs/shared/providers/shell/I18nProv
|
|
|
9
9
|
import { AlertCircle as d, ArrowLeft as f, Loader2 as p, Save as m, Shield as h } from "lucide-react";
|
|
10
10
|
import { useNavigate as g, useParams as _ } from "react-router-dom";
|
|
11
11
|
import { jsx as v, jsxs as y } from "react/jsx-runtime";
|
|
12
|
+
import { GlassCard as b } from "@burdenoff/fe-libs/ui";
|
|
12
13
|
//#region src/channel/modules/channels/pages/EditConnectionPage.tsx
|
|
13
|
-
function
|
|
14
|
-
let
|
|
15
|
-
let n =
|
|
14
|
+
function x() {
|
|
15
|
+
let x = g(), { connectionId: S } = _(), { authToken: C, apiGatewayUrl: w, organizationId: T } = e(), { t: E } = u(), D = (e, t) => {
|
|
16
|
+
let n = E(e);
|
|
16
17
|
return n === e ? t : n;
|
|
17
|
-
}, [
|
|
18
|
+
}, [O, k] = l(null), [A, j] = l(!0), [M, N] = l(null), [P, F] = l(!0), [I, L] = l(""), [R, z] = l(""), [B, V] = l(!1);
|
|
18
19
|
return c(() => {
|
|
19
|
-
if (!
|
|
20
|
+
if (!S) return;
|
|
20
21
|
let e = !1;
|
|
21
22
|
return (async () => {
|
|
22
|
-
|
|
23
|
+
j(!0), N(null);
|
|
23
24
|
try {
|
|
24
|
-
let t = await r(
|
|
25
|
+
let t = await r(w, C, T).query({
|
|
25
26
|
query: i,
|
|
26
|
-
variables: { id:
|
|
27
|
+
variables: { id: S },
|
|
27
28
|
fetchPolicy: "network-only"
|
|
28
29
|
});
|
|
29
30
|
if (e) return;
|
|
30
31
|
let n = t.data?.getConnection ?? null;
|
|
31
|
-
|
|
32
|
+
k(n), F(n?.isActive ?? !0);
|
|
32
33
|
} catch (t) {
|
|
33
34
|
if (e) return;
|
|
34
|
-
|
|
35
|
+
N(t instanceof Error ? t.message : D("connection.loadFailed", "Failed to load connection"));
|
|
35
36
|
} finally {
|
|
36
|
-
e ||
|
|
37
|
+
e || j(!1);
|
|
37
38
|
}
|
|
38
39
|
})(), () => {
|
|
39
40
|
e = !0;
|
|
40
41
|
};
|
|
41
42
|
}, [
|
|
42
|
-
x,
|
|
43
|
-
C,
|
|
44
43
|
S,
|
|
45
44
|
w,
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
C,
|
|
46
|
+
T,
|
|
47
|
+
D
|
|
48
|
+
]), A ? /* @__PURE__ */ v("div", {
|
|
48
49
|
className: "container mx-auto p-6 flex items-center justify-center min-h-64",
|
|
49
50
|
children: /* @__PURE__ */ v(p, { className: "size-6 animate-spin text-fg-muted" })
|
|
50
|
-
}) :
|
|
51
|
+
}) : O ? /* @__PURE__ */ v(o, {
|
|
51
52
|
permission: s.CHANNEL_CONNECTION_UPDATE,
|
|
52
53
|
children: /* @__PURE__ */ y("div", {
|
|
53
54
|
className: "container mx-auto p-6 max-w-2xl",
|
|
54
55
|
children: [
|
|
55
56
|
/* @__PURE__ */ y("button", {
|
|
56
57
|
type: "button",
|
|
57
|
-
onClick: () =>
|
|
58
|
+
onClick: () => x(-1),
|
|
58
59
|
className: "flex items-center gap-2 text-fg-muted hover:text-fg mb-6",
|
|
59
|
-
children: [/* @__PURE__ */ v(f, { className: "size-4 " }),
|
|
60
|
+
children: [/* @__PURE__ */ v(f, { className: "size-4 " }), D("common.back", "Back")]
|
|
60
61
|
}),
|
|
61
62
|
/* @__PURE__ */ y("div", {
|
|
62
63
|
className: "flex items-center gap-3 mb-6",
|
|
63
64
|
children: [/* @__PURE__ */ v(h, { className: "size-7 text-accent-blue" }), /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("h1", {
|
|
64
65
|
className: "text-2xl font-bold text-fg",
|
|
65
|
-
children:
|
|
66
|
+
children: D("connection.edit.title", "Edit Connection")
|
|
66
67
|
}), /* @__PURE__ */ v("p", {
|
|
67
68
|
className: "text-sm text-fg-muted",
|
|
68
|
-
children:
|
|
69
|
+
children: D("connection.edit.subtitle", "Update credentials, status, or platform metadata.")
|
|
69
70
|
})] })]
|
|
70
71
|
}),
|
|
71
|
-
|
|
72
|
+
M && /* @__PURE__ */ y("div", {
|
|
72
73
|
className: "bg-status-error-bg-subtle border border-status-error-bg-subtle rounded-lg p-4 mb-6 flex gap-3",
|
|
73
74
|
children: [/* @__PURE__ */ v(d, { className: "size-5 text-status-error-text shrink-0" }), /* @__PURE__ */ v("p", {
|
|
74
75
|
className: "text-sm text-status-error-text",
|
|
75
|
-
children:
|
|
76
|
+
children: M
|
|
76
77
|
})]
|
|
77
78
|
}),
|
|
78
|
-
/* @__PURE__ */
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
79
|
+
/* @__PURE__ */ v(b, {
|
|
80
|
+
treatment: "plain",
|
|
81
|
+
className: "border-border-seam p-6",
|
|
82
|
+
children: /* @__PURE__ */ y("form", {
|
|
83
|
+
onSubmit: async (e) => {
|
|
84
|
+
if (e.preventDefault(), !S) return;
|
|
85
|
+
N(null);
|
|
86
|
+
let i;
|
|
87
|
+
if (I.trim()) try {
|
|
88
|
+
i = JSON.parse(I);
|
|
89
|
+
} catch {
|
|
90
|
+
N(D("connection.validation.credentialsJson", "Credentials must be valid JSON"));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
let o;
|
|
94
|
+
if (R.trim()) try {
|
|
95
|
+
o = JSON.parse(R);
|
|
96
|
+
} catch {
|
|
97
|
+
N(D("connection.validation.metadataJson", "Platform metadata must be valid JSON"));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
V(!0);
|
|
101
|
+
try {
|
|
102
|
+
await r(w, C, T).mutate({
|
|
103
|
+
mutation: a,
|
|
104
|
+
variables: {
|
|
105
|
+
id: S,
|
|
106
|
+
input: {
|
|
107
|
+
isActive: P,
|
|
108
|
+
credentials: i,
|
|
109
|
+
platformMetadata: o
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}), t("connection.updated", { id: S }), x(`../${S}`);
|
|
113
|
+
} catch (e) {
|
|
114
|
+
n(e, {
|
|
115
|
+
module: "channel",
|
|
116
|
+
context: {
|
|
117
|
+
action: "EditConnectionPage.submit",
|
|
118
|
+
connectionId: S
|
|
106
119
|
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
D.
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
onChange: (e) =>
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}), /* @__PURE__ */ v("button", {
|
|
180
|
-
type: "button",
|
|
181
|
-
onClick: () => b(-1),
|
|
182
|
-
className: "px-4 py-2 text-fg border border-border-subtle rounded-md",
|
|
183
|
-
children: E("common.cancel", "Cancel")
|
|
184
|
-
})]
|
|
185
|
-
})
|
|
186
|
-
]
|
|
120
|
+
}, "[channel] Failed to update connection"), N(e instanceof Error ? e.message : D("connection.updateFailed", "Failed to update connection"));
|
|
121
|
+
} finally {
|
|
122
|
+
V(!1);
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
className: "space-y-6",
|
|
126
|
+
children: [
|
|
127
|
+
/* @__PURE__ */ y("div", {
|
|
128
|
+
className: "text-sm text-fg-muted",
|
|
129
|
+
children: [
|
|
130
|
+
/* @__PURE__ */ v("span", {
|
|
131
|
+
className: "font-medium text-fg",
|
|
132
|
+
children: "ID:"
|
|
133
|
+
}),
|
|
134
|
+
" ",
|
|
135
|
+
O.id,
|
|
136
|
+
/* @__PURE__ */ v("br", {}),
|
|
137
|
+
/* @__PURE__ */ y("span", {
|
|
138
|
+
className: "font-medium text-fg",
|
|
139
|
+
children: [D("connection.field.authType", "Auth Type"), ":"]
|
|
140
|
+
}),
|
|
141
|
+
" ",
|
|
142
|
+
O.authType
|
|
143
|
+
]
|
|
144
|
+
}),
|
|
145
|
+
/* @__PURE__ */ v("div", { children: /* @__PURE__ */ y("label", {
|
|
146
|
+
className: "flex items-center gap-2 text-sm font-medium text-fg",
|
|
147
|
+
children: [/* @__PURE__ */ v("input", {
|
|
148
|
+
type: "checkbox",
|
|
149
|
+
checked: P,
|
|
150
|
+
onChange: (e) => F(e.target.checked)
|
|
151
|
+
}), D("connection.field.isActive", "Active")]
|
|
152
|
+
}) }),
|
|
153
|
+
/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
|
|
154
|
+
className: "block text-sm font-medium text-fg mb-2",
|
|
155
|
+
children: D("connection.field.credentials", "Credentials (JSON)")
|
|
156
|
+
}), /* @__PURE__ */ v("textarea", {
|
|
157
|
+
"aria-label": D("connection.field.credentialsLeaveBlank", "Leave blank to keep existing credentials"),
|
|
158
|
+
value: I,
|
|
159
|
+
onChange: (e) => L(e.target.value),
|
|
160
|
+
rows: 6,
|
|
161
|
+
className: "w-full px-3 py-2 bg-bg border border-border-subtle rounded-md font-mono text-sm text-fg",
|
|
162
|
+
placeholder: D("connection.field.credentialsLeaveBlank", "Leave blank to keep existing credentials")
|
|
163
|
+
})] }),
|
|
164
|
+
/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
|
|
165
|
+
htmlFor: "edit-conn-platform-metadata",
|
|
166
|
+
className: "block text-sm font-medium text-fg mb-2",
|
|
167
|
+
children: D("connection.field.platformMetadata", "Platform Metadata (JSON)")
|
|
168
|
+
}), /* @__PURE__ */ v("textarea", {
|
|
169
|
+
id: "edit-conn-platform-metadata",
|
|
170
|
+
"aria-label": D("connection.field.platformMetadata", "Platform Metadata (JSON)"),
|
|
171
|
+
value: R,
|
|
172
|
+
onChange: (e) => z(e.target.value),
|
|
173
|
+
rows: 4,
|
|
174
|
+
className: "w-full px-3 py-2 bg-bg border border-border-subtle rounded-md font-mono text-sm text-fg"
|
|
175
|
+
})] }),
|
|
176
|
+
/* @__PURE__ */ y("div", {
|
|
177
|
+
className: "flex gap-3 pt-2",
|
|
178
|
+
children: [/* @__PURE__ */ y("button", {
|
|
179
|
+
type: "submit",
|
|
180
|
+
disabled: B,
|
|
181
|
+
className: "flex items-center gap-2 px-4 py-2 bg-accent-blue text-fg-on-accent rounded-md disabled:opacity-50",
|
|
182
|
+
children: [B ? /* @__PURE__ */ v(p, { className: "size-4 animate-spin" }) : /* @__PURE__ */ v(m, { className: "size-4 " }), D("common.save", "Save")]
|
|
183
|
+
}), /* @__PURE__ */ v("button", {
|
|
184
|
+
type: "button",
|
|
185
|
+
onClick: () => x(-1),
|
|
186
|
+
className: "px-4 py-2 text-fg border border-border-subtle rounded-md",
|
|
187
|
+
children: D("common.cancel", "Cancel")
|
|
188
|
+
})]
|
|
189
|
+
})
|
|
190
|
+
]
|
|
191
|
+
})
|
|
187
192
|
})
|
|
188
193
|
]
|
|
189
194
|
})
|
|
@@ -193,12 +198,12 @@ function b() {
|
|
|
193
198
|
className: "bg-status-error-bg-subtle border border-status-error-bg-subtle rounded-lg p-4 flex gap-3",
|
|
194
199
|
children: [/* @__PURE__ */ v(d, { className: "size-5 text-status-error-text shrink-0" }), /* @__PURE__ */ v("p", {
|
|
195
200
|
className: "text-sm text-status-error-text",
|
|
196
|
-
children:
|
|
201
|
+
children: M ?? D("connection.notFound", "Connection not found")
|
|
197
202
|
})]
|
|
198
203
|
})
|
|
199
204
|
});
|
|
200
205
|
}
|
|
201
206
|
//#endregion
|
|
202
|
-
export {
|
|
207
|
+
export { x as EditConnectionPage };
|
|
203
208
|
|
|
204
209
|
//# sourceMappingURL=EditConnectionPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EditConnectionPage.js","names":[],"sources":["../../../../../src/channel/modules/channels/pages/EditConnectionPage.tsx"],"sourcesContent":["/**\n * Edit Connection Page\n *\n * Standalone page mounted at /channels/connections/:connectionId/edit.\n * Fetches the connection directly via getConnection (no channelId required in URL).\n *\n * Form fields match UpdateConnectionInput exactly.\n */\n\nimport { useEffect, useState } from 'react';\nimport { useNavigate, useParams } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ArrowLeft, Loader2, AlertCircle, Save, Shield } from 'lucide-react';\nimport { useChannel as useChannelContext } from '../../../providers/ChannelProvider';\nimport { getGlobalChannelClient } from '../../../graphql/global-client';\nimport {\n GetConnectionDocument,\n UpdateConnectionDocument,\n type GetConnectionQuery,\n type UpdateConnectionMutation,\n} from '../../../../generated/global-operations';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\nimport { emitShellEvent, reportShellError } from '../../../utils/shellBridge';\n\ntype Connection = NonNullable<GetConnectionQuery['getConnection']>;\n\nexport function EditConnectionPage() {\n const navigate = useNavigate();\n const { connectionId } = useParams<{ connectionId: string }>();\n const { authToken, apiGatewayUrl, organizationId } = useChannelContext();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n const [connection, setConnection] = useState<Connection | null>(null);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n const [isActive, setIsActive] = useState(true);\n const [credentials, setCredentials] = useState('');\n const [platformMetadata, setPlatformMetadata] = useState('');\n const [isSubmitting, setIsSubmitting] = useState(false);\n\n useEffect(() => {\n if (!connectionId) return;\n let cancelled = false;\n void (async () => {\n setIsLoading(true);\n setError(null);\n try {\n const client = getGlobalChannelClient(apiGatewayUrl, authToken, organizationId);\n const result = await client.query<GetConnectionQuery>({\n query: GetConnectionDocument,\n variables: { id: connectionId },\n fetchPolicy: 'network-only',\n });\n if (cancelled) return;\n const conn = result.data?.getConnection ?? null;\n setConnection(conn);\n setIsActive(conn?.isActive ?? true);\n } catch (err: unknown) {\n if (cancelled) return;\n setError(\n err instanceof Error\n ? err.message\n : tr('connection.loadFailed', 'Failed to load connection')\n );\n } finally {\n if (!cancelled) setIsLoading(false);\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [connectionId, apiGatewayUrl, authToken, organizationId, tr]);\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!connectionId) return;\n setError(null);\n\n let parsedCredentials: Record<string, unknown> | undefined;\n if (credentials.trim()) {\n try {\n parsedCredentials = JSON.parse(credentials);\n } catch {\n setError(tr('connection.validation.credentialsJson', 'Credentials must be valid JSON'));\n return;\n }\n }\n\n let parsedMetadata: Record<string, unknown> | undefined;\n if (platformMetadata.trim()) {\n try {\n parsedMetadata = JSON.parse(platformMetadata);\n } catch {\n setError(tr('connection.validation.metadataJson', 'Platform metadata must be valid JSON'));\n return;\n }\n }\n\n setIsSubmitting(true);\n try {\n const client = getGlobalChannelClient(apiGatewayUrl, authToken, organizationId);\n await client.mutate<UpdateConnectionMutation>({\n mutation: UpdateConnectionDocument,\n variables: {\n id: connectionId,\n input: {\n isActive,\n credentials: parsedCredentials,\n platformMetadata: parsedMetadata,\n },\n },\n });\n emitShellEvent('connection.updated', { id: connectionId });\n navigate(`../${connectionId}`);\n } catch (err: unknown) {\n reportShellError(\n err,\n { module: 'channel', context: { action: 'EditConnectionPage.submit', connectionId } },\n '[channel] Failed to update connection'\n );\n setError(\n err instanceof Error\n ? err.message\n : tr('connection.updateFailed', 'Failed to update connection')\n );\n } finally {\n setIsSubmitting(false);\n }\n };\n\n if (isLoading) {\n return (\n <div className=\"container mx-auto p-6 flex items-center justify-center min-h-64\">\n <Loader2 className=\"size-6 animate-spin text-fg-muted\" />\n </div>\n );\n }\n\n if (!connection) {\n return (\n <div className=\"container mx-auto p-6 max-w-2xl\">\n <div className=\"bg-status-error-bg-subtle border border-status-error-bg-subtle rounded-lg p-4 flex gap-3\">\n <AlertCircle className=\"size-5 text-status-error-text shrink-0\" />\n <p className=\"text-sm text-status-error-text\">\n {error ?? tr('connection.notFound', 'Connection not found')}\n </p>\n </div>\n </div>\n );\n }\n\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_CONNECTION_UPDATE}>\n <div className=\"container mx-auto p-6 max-w-2xl\">\n <button\n type=\"button\"\n onClick={() => navigate(-1)}\n className=\"flex items-center gap-2 text-fg-muted hover:text-fg mb-6\"\n >\n <ArrowLeft className=\"size-4 \" />\n {tr('common.back', 'Back')}\n </button>\n\n <div className=\"flex items-center gap-3 mb-6\">\n <Shield className=\"size-7 text-accent-blue\" />\n <div>\n <h1 className=\"text-2xl font-bold text-fg\">\n {tr('connection.edit.title', 'Edit Connection')}\n </h1>\n <p className=\"text-sm text-fg-muted\">\n {tr('connection.edit.subtitle', 'Update credentials, status, or platform metadata.')}\n </p>\n </div>\n </div>\n\n {error && (\n <div className=\"bg-status-error-bg-subtle border border-status-error-bg-subtle rounded-lg p-4 mb-6 flex gap-3\">\n <AlertCircle className=\"size-5 text-status-error-text shrink-0\" />\n <p className=\"text-sm text-status-error-text\">{error}</p>\n </div>\n )}\n\n <form\n onSubmit={handleSubmit}\n className=\"space-y-6 bg-bg-elevated rounded-lg p-6 border border-border-subtle\"\n >\n <div className=\"text-sm text-fg-muted\">\n <span className=\"font-medium text-fg\">ID:</span> {connection.id}\n <br />\n <span className=\"font-medium text-fg\">\n {tr('connection.field.authType', 'Auth Type')}:\n </span>{' '}\n {connection.authType}\n </div>\n\n <div>\n <label className=\"flex items-center gap-2 text-sm font-medium text-fg\">\n <input\n type=\"checkbox\"\n checked={isActive}\n onChange={(e) => setIsActive(e.target.checked)}\n />\n {tr('connection.field.isActive', 'Active')}\n </label>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-fg mb-2\">\n {tr('connection.field.credentials', 'Credentials (JSON)')}\n </label>\n <textarea\n aria-label={tr(\n 'connection.field.credentialsLeaveBlank',\n 'Leave blank to keep existing credentials'\n )}\n value={credentials}\n onChange={(e) => setCredentials(e.target.value)}\n rows={6}\n className=\"w-full px-3 py-2 bg-bg border border-border-subtle rounded-md font-mono text-sm text-fg\"\n placeholder={tr(\n 'connection.field.credentialsLeaveBlank',\n 'Leave blank to keep existing credentials'\n )}\n />\n </div>\n\n <div>\n <label\n htmlFor=\"edit-conn-platform-metadata\"\n className=\"block text-sm font-medium text-fg mb-2\"\n >\n {tr('connection.field.platformMetadata', 'Platform Metadata (JSON)')}\n </label>\n <textarea\n id=\"edit-conn-platform-metadata\"\n aria-label={tr('connection.field.platformMetadata', 'Platform Metadata (JSON)')}\n value={platformMetadata}\n onChange={(e) => setPlatformMetadata(e.target.value)}\n rows={4}\n className=\"w-full px-3 py-2 bg-bg border border-border-subtle rounded-md font-mono text-sm text-fg\"\n />\n </div>\n\n <div className=\"flex gap-3 pt-2\">\n <button\n type=\"submit\"\n disabled={isSubmitting}\n className=\"flex items-center gap-2 px-4 py-2 bg-accent-blue text-fg-on-accent rounded-md disabled:opacity-50\"\n >\n {isSubmitting ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Save className=\"size-4 \" />\n )}\n {tr('common.save', 'Save')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigate(-1)}\n className=\"px-4 py-2 text-fg border border-border-subtle rounded-md\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n </div>\n </form>\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EditConnectionPage.js","names":[],"sources":["../../../../../src/channel/modules/channels/pages/EditConnectionPage.tsx"],"sourcesContent":["/**\n * Edit Connection Page\n *\n * Standalone page mounted at /channels/connections/:connectionId/edit.\n * Fetches the connection directly via getConnection (no channelId required in URL).\n *\n * Form fields match UpdateConnectionInput exactly.\n */\n\nimport { useEffect, useState } from 'react';\nimport { useNavigate, useParams } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ArrowLeft, Loader2, AlertCircle, Save, Shield } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { useChannel as useChannelContext } from '../../../providers/ChannelProvider';\nimport { getGlobalChannelClient } from '../../../graphql/global-client';\nimport {\n GetConnectionDocument,\n UpdateConnectionDocument,\n type GetConnectionQuery,\n type UpdateConnectionMutation,\n} from '../../../../generated/global-operations';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\nimport { emitShellEvent, reportShellError } from '../../../utils/shellBridge';\n\ntype Connection = NonNullable<GetConnectionQuery['getConnection']>;\n\nexport function EditConnectionPage() {\n const navigate = useNavigate();\n const { connectionId } = useParams<{ connectionId: string }>();\n const { authToken, apiGatewayUrl, organizationId } = useChannelContext();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n const [connection, setConnection] = useState<Connection | null>(null);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n const [isActive, setIsActive] = useState(true);\n const [credentials, setCredentials] = useState('');\n const [platformMetadata, setPlatformMetadata] = useState('');\n const [isSubmitting, setIsSubmitting] = useState(false);\n\n useEffect(() => {\n if (!connectionId) return;\n let cancelled = false;\n void (async () => {\n setIsLoading(true);\n setError(null);\n try {\n const client = getGlobalChannelClient(apiGatewayUrl, authToken, organizationId);\n const result = await client.query<GetConnectionQuery>({\n query: GetConnectionDocument,\n variables: { id: connectionId },\n fetchPolicy: 'network-only',\n });\n if (cancelled) return;\n const conn = result.data?.getConnection ?? null;\n setConnection(conn);\n setIsActive(conn?.isActive ?? true);\n } catch (err: unknown) {\n if (cancelled) return;\n setError(\n err instanceof Error\n ? err.message\n : tr('connection.loadFailed', 'Failed to load connection')\n );\n } finally {\n if (!cancelled) setIsLoading(false);\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [connectionId, apiGatewayUrl, authToken, organizationId, tr]);\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!connectionId) return;\n setError(null);\n\n let parsedCredentials: Record<string, unknown> | undefined;\n if (credentials.trim()) {\n try {\n parsedCredentials = JSON.parse(credentials);\n } catch {\n setError(tr('connection.validation.credentialsJson', 'Credentials must be valid JSON'));\n return;\n }\n }\n\n let parsedMetadata: Record<string, unknown> | undefined;\n if (platformMetadata.trim()) {\n try {\n parsedMetadata = JSON.parse(platformMetadata);\n } catch {\n setError(tr('connection.validation.metadataJson', 'Platform metadata must be valid JSON'));\n return;\n }\n }\n\n setIsSubmitting(true);\n try {\n const client = getGlobalChannelClient(apiGatewayUrl, authToken, organizationId);\n await client.mutate<UpdateConnectionMutation>({\n mutation: UpdateConnectionDocument,\n variables: {\n id: connectionId,\n input: {\n isActive,\n credentials: parsedCredentials,\n platformMetadata: parsedMetadata,\n },\n },\n });\n emitShellEvent('connection.updated', { id: connectionId });\n navigate(`../${connectionId}`);\n } catch (err: unknown) {\n reportShellError(\n err,\n { module: 'channel', context: { action: 'EditConnectionPage.submit', connectionId } },\n '[channel] Failed to update connection'\n );\n setError(\n err instanceof Error\n ? err.message\n : tr('connection.updateFailed', 'Failed to update connection')\n );\n } finally {\n setIsSubmitting(false);\n }\n };\n\n if (isLoading) {\n return (\n <div className=\"container mx-auto p-6 flex items-center justify-center min-h-64\">\n <Loader2 className=\"size-6 animate-spin text-fg-muted\" />\n </div>\n );\n }\n\n if (!connection) {\n return (\n <div className=\"container mx-auto p-6 max-w-2xl\">\n <div className=\"bg-status-error-bg-subtle border border-status-error-bg-subtle rounded-lg p-4 flex gap-3\">\n <AlertCircle className=\"size-5 text-status-error-text shrink-0\" />\n <p className=\"text-sm text-status-error-text\">\n {error ?? tr('connection.notFound', 'Connection not found')}\n </p>\n </div>\n </div>\n );\n }\n\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_CONNECTION_UPDATE}>\n <div className=\"container mx-auto p-6 max-w-2xl\">\n <button\n type=\"button\"\n onClick={() => navigate(-1)}\n className=\"flex items-center gap-2 text-fg-muted hover:text-fg mb-6\"\n >\n <ArrowLeft className=\"size-4 \" />\n {tr('common.back', 'Back')}\n </button>\n\n <div className=\"flex items-center gap-3 mb-6\">\n <Shield className=\"size-7 text-accent-blue\" />\n <div>\n <h1 className=\"text-2xl font-bold text-fg\">\n {tr('connection.edit.title', 'Edit Connection')}\n </h1>\n <p className=\"text-sm text-fg-muted\">\n {tr('connection.edit.subtitle', 'Update credentials, status, or platform metadata.')}\n </p>\n </div>\n </div>\n\n {error && (\n <div className=\"bg-status-error-bg-subtle border border-status-error-bg-subtle rounded-lg p-4 mb-6 flex gap-3\">\n <AlertCircle className=\"size-5 text-status-error-text shrink-0\" />\n <p className=\"text-sm text-status-error-text\">{error}</p>\n </div>\n )}\n\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-6\">\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n <div className=\"text-sm text-fg-muted\">\n <span className=\"font-medium text-fg\">ID:</span> {connection.id}\n <br />\n <span className=\"font-medium text-fg\">\n {tr('connection.field.authType', 'Auth Type')}:\n </span>{' '}\n {connection.authType}\n </div>\n\n <div>\n <label className=\"flex items-center gap-2 text-sm font-medium text-fg\">\n <input\n type=\"checkbox\"\n checked={isActive}\n onChange={(e) => setIsActive(e.target.checked)}\n />\n {tr('connection.field.isActive', 'Active')}\n </label>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-fg mb-2\">\n {tr('connection.field.credentials', 'Credentials (JSON)')}\n </label>\n <textarea\n aria-label={tr(\n 'connection.field.credentialsLeaveBlank',\n 'Leave blank to keep existing credentials'\n )}\n value={credentials}\n onChange={(e) => setCredentials(e.target.value)}\n rows={6}\n className=\"w-full px-3 py-2 bg-bg border border-border-subtle rounded-md font-mono text-sm text-fg\"\n placeholder={tr(\n 'connection.field.credentialsLeaveBlank',\n 'Leave blank to keep existing credentials'\n )}\n />\n </div>\n\n <div>\n <label\n htmlFor=\"edit-conn-platform-metadata\"\n className=\"block text-sm font-medium text-fg mb-2\"\n >\n {tr('connection.field.platformMetadata', 'Platform Metadata (JSON)')}\n </label>\n <textarea\n id=\"edit-conn-platform-metadata\"\n aria-label={tr('connection.field.platformMetadata', 'Platform Metadata (JSON)')}\n value={platformMetadata}\n onChange={(e) => setPlatformMetadata(e.target.value)}\n rows={4}\n className=\"w-full px-3 py-2 bg-bg border border-border-subtle rounded-md font-mono text-sm text-fg\"\n />\n </div>\n\n <div className=\"flex gap-3 pt-2\">\n <button\n type=\"submit\"\n disabled={isSubmitting}\n className=\"flex items-center gap-2 px-4 py-2 bg-accent-blue text-fg-on-accent rounded-md disabled:opacity-50\"\n >\n {isSubmitting ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Save className=\"size-4 \" />\n )}\n {tr('common.save', 'Save')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigate(-1)}\n className=\"px-4 py-2 text-fg border border-border-subtle rounded-md\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n </div>\n </form>\n </GlassCard>\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
|