@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":"CreateChannelPage.js","names":[],"sources":["../../../../../src/channel/modules/channels/pages/CreateChannelPage.tsx"],"sourcesContent":["/**\n * Create Channel Page\n *\n * Form fields match the exact CreateChannelInput from global-channel-svc:\n * name (required), platform (required), organizationId (auto), description,\n * adapterType, status, priority, platformConfig\n *\n * organizationId is injected from shell context — not shown in form.\n * Credentials belong to createConnection mutation — not part of channel creation.\n */\n\nimport { useState } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { OptionalFields } from '@burdenoff/fe-libs/ui';\nimport {\n Radio,\n ArrowLeft,\n Save,\n Loader2,\n AlertCircle,\n Mail,\n Smartphone,\n Bell,\n Inbox,\n Globe,\n Hash,\n Users,\n MessageCircle,\n Send,\n MessageSquare,\n CheckCircle2,\n} from 'lucide-react';\nimport { useChannel } from '../../../providers/ChannelProvider';\nimport {\n useChannels,\n type ChannelPlatform,\n type AdapterType,\n type ChannelStatus,\n type CreateChannelInput,\n} from '../../../hooks/useChannels';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\n\n// ============================================================================\n// PLATFORM CONFIG\n// ============================================================================\n\n// NOTE: AdapterType values MUST match the backend Prisma schema exactly.\n// See: global-channel-svc/prisma/schema/schema.prisma (enum AdapterType)\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-accent-blue bg-accent-blue-subtle border-accent-blue-subtle',\n adapters: [\n { value: 'AWS_SES', label: 'AWS SES' },\n { value: 'AZURE_ACS', label: 'Azure Communication Services' },\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-bg-subtle',\n adapters: [\n { value: 'AWS_SNS', label: 'AWS SNS' },\n { value: 'TWILIO_SMS', label: 'Twilio SMS' },\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-subtle',\n adapters: [{ value: 'FCM', label: 'Firebase Cloud Messaging' }],\n },\n {\n value: 'INAPP',\n label: 'In-App',\n description: 'In-app notifications',\n icon: Inbox,\n color: 'text-accent-indigo bg-accent-indigo-subtle border-accent-indigo-subtle',\n adapters: [{ value: 'INAPP_NATIVE', label: 'Native In-App' }],\n },\n {\n value: 'SLACK',\n label: 'Slack',\n description: 'Workspace notifications',\n icon: Hash,\n color: 'text-accent-pink bg-accent-pink-subtle border-accent-pink-subtle',\n adapters: [{ value: 'SLACK_BOT', label: 'Slack Bot' }],\n },\n {\n value: 'TEAMS',\n label: 'Teams',\n description: 'Teams notifications',\n icon: Users,\n color: 'text-accent-cyan bg-accent-cyan-subtle border-accent-cyan-subtle',\n adapters: [{ value: 'TEAMS_BOT', label: 'Teams Bot' }],\n },\n {\n value: 'DISCORD',\n label: 'Discord',\n description: 'Server notifications',\n icon: MessageCircle,\n color: 'text-accent-violet bg-accent-violet-subtle border-accent-violet-subtle',\n adapters: [{ value: 'DISCORD_BOT', label: 'Discord Bot' }],\n },\n {\n value: 'WHATSAPP',\n label: 'WhatsApp',\n description: 'Business messaging',\n icon: Send,\n color: 'text-accent-emerald bg-accent-emerald-subtle border-accent-emerald-subtle',\n adapters: [\n { value: 'WHATSAPP_CLOUD', label: 'WhatsApp Cloud API' },\n { value: 'WHATSAPP_TWILIO', label: 'WhatsApp via Twilio' },\n ],\n },\n {\n value: 'WEBCHAT',\n label: 'Webchat',\n description: 'Chat widgets',\n icon: MessageSquare,\n color: 'text-accent-teal bg-accent-teal-subtle border-accent-teal-subtle',\n adapters: [], // No WEBCHAT adapters in backend Prisma schema yet\n },\n {\n value: 'WEBHOOK',\n label: 'Webhook',\n description: 'HTTP webhooks',\n icon: Globe,\n color: 'text-accent-orange bg-accent-orange-subtle border-accent-orange-subtle',\n adapters: [{ value: 'WEBHOOK_HTTP', label: 'HTTP Webhook' }],\n },\n];\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\nexport function CreateChannelPage() {\n const navigate = useNavigate();\n const { organizationId } = useChannel();\n const { createChannel } = 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 const [isSubmitting, setIsSubmitting] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n // Form state — matches CreateChannelInput exactly\n const [formData, setFormData] = useState<{\n name: string; // required\n platform: ChannelPlatform; // required\n description: string; // optional\n adapterType: AdapterType | ''; // required (auto-selected based on platform)\n status: ChannelStatus; // optional — default ACTIVE on server\n priority: number; // optional — default 100 on server\n platformConfig: string; // optional — JSON string\n }>({\n name: '',\n platform: 'EMAIL',\n description: '',\n adapterType: 'AWS_SES', // Default to AWS SES for Email platform\n status: 'ACTIVE',\n priority: 100,\n platformConfig: '',\n });\n\n const availableAdapters =\n PLATFORM_OPTIONS.find((p) => p.value === formData.platform)?.adapters || [];\n\n const selectedPlatform = PLATFORM_OPTIONS.find((p) => p.value === formData.platform);\n\n const handlePlatformChange = (platform: ChannelPlatform) => {\n setFormData((prev) => ({ ...prev, platform, adapterType: '' }));\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n\n if (!formData.name.trim()) {\n setError(tr('channel.validation.nameRequired', 'Channel name is required'));\n return;\n }\n\n if (!organizationId) {\n setError(\n tr(\n 'channel.validation.organizationRequired',\n 'Organization context is required. Please ensure an organization is selected.'\n )\n );\n return;\n }\n\n // Validate platformConfig JSON if provided\n let parsedPlatformConfig: Record<string, unknown> | undefined;\n if (formData.platformConfig.trim()) {\n try {\n parsedPlatformConfig = JSON.parse(formData.platformConfig);\n } catch {\n setError(tr('channel.validation.platformConfigJson', 'Platform Config must be valid JSON'));\n return;\n }\n }\n\n setIsSubmitting(true);\n try {\n const input: CreateChannelInput = {\n name: formData.name.trim(),\n platform: formData.platform,\n organizationId,\n description: formData.description.trim() || undefined,\n adapterType: (formData.adapterType as AdapterType) || undefined,\n status: formData.status,\n priority: formData.priority,\n platformConfig: parsedPlatformConfig,\n };\n\n const result = await createChannel(input);\n if (result) {\n navigate(`../${result.id}`);\n } else {\n setError(tr('channel.createFailed', 'Failed to create channel'));\n }\n } catch (err: unknown) {\n setError(\n err instanceof Error ? err.message : tr('channel.createFailed', 'Failed to create channel')\n );\n } finally {\n setIsSubmitting(false);\n }\n };\n\n // ── Render ────────────────────────────────────────────────────────────\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_CREATE}>\n <div className=\"p-3 sm:p-6 space-y-6 max-w-5xl mx-auto\">\n {/* ── Header ───────────────────────────────────────────────────── */}\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => navigate(-1)}\n className=\"p-2 text-text-secondary hover:text-text-primary hover:bg-surface-hover rounded-lg transition-colors\"\n >\n <ArrowLeft size={20} />\n </button>\n <div>\n <h1 className=\"text-xl font-bold text-text-primary flex items-center gap-2\">\n <Radio size={22} className=\"text-accent-primary\" />\n {tr('channel.createChannel', 'Create Channel')}\n </h1>\n <p className=\"text-sm text-text-secondary mt-0.5\">\n {tr('channel.createChannelSubtitle', 'Set up a new communication channel')}\n </p>\n </div>\n </div>\n </div>\n\n {/* ── Error ────────────────────────────────────────────────────── */}\n {error && (\n <div className=\"flex items-center gap-3 p-3 bg-error-500/10 border border-status-error-border/30 rounded-lg\">\n <AlertCircle size={16} className=\"text-status-error-text shrink-0\" />\n <p className=\"text-sm text-error-400\">{error}</p>\n </div>\n )}\n\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n {/* ── Row 1: Name + Description | Status + Provider (2-col) ─── */}\n <div className=\"grid gap-5 lg:grid-cols-2\">\n {/* Left: Name & Description */}\n <div className=\"p-5 bg-surface border border-border-subtle rounded-lg space-y-4\">\n <h2 className=\"text-sm font-semibold text-text-primary uppercase tracking-wider\">\n {tr('channel.basicInformation', 'Basic Information')}\n </h2>\n\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">\n {tr('channel.name', 'Name')} <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n aria-label={tr('channel.namePlaceholder', 'e.g., Primary Email, SMS Alerts')}\n type=\"text\"\n value={formData.name}\n onChange={(e) => setFormData((prev) => ({ ...prev, name: e.target.value }))}\n className=\"w-full px-3 py-2 text-sm bg-surface border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50 placeholder:text-text-secondary\"\n placeholder={tr('channel.namePlaceholder', 'e.g., Primary Email, SMS Alerts')}\n />\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">\n {tr('channel.description', 'Description')}\n </label>\n <textarea\n aria-label={tr(\n 'channel.descriptionPlaceholder',\n \"Optional description of this channel's purpose\"\n )}\n value={formData.description}\n onChange={(e) =>\n setFormData((prev) => ({ ...prev, description: e.target.value }))\n }\n rows={3}\n className=\"w-full px-3 py-2 text-sm bg-surface border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50 resize-none placeholder:text-text-secondary\"\n placeholder={tr(\n 'channel.descriptionPlaceholder',\n \"Optional description of this channel's purpose\"\n )}\n />\n </div>\n </div>\n\n {/* Right: Status + Provider / Adapter */}\n <div className=\"p-5 bg-surface border border-border-subtle rounded-lg space-y-4\">\n <h2 className=\"text-sm font-semibold text-text-primary uppercase tracking-wider\">\n {tr('channel.providerStatus', 'Provider & Status')}\n </h2>\n\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">\n {tr('channel.providerAdapter', 'Provider / Adapter')}\n </label>\n <select\n value={formData.adapterType}\n onChange={(e) =>\n setFormData((prev) => ({\n ...prev,\n adapterType: e.target.value as AdapterType | '',\n }))\n }\n className=\"w-full px-3 py-2 text-sm bg-surface border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50 text-text-primary\"\n >\n <option value=\"\">\n {tr('channel.selectProvider', 'Select a provider (optional)')}\n </option>\n {availableAdapters.map((a) => (\n <option key={a.value} value={a.value}>\n {a.label}\n </option>\n ))}\n </select>\n <p className=\"mt-1 text-[11px] text-text-secondary\">\n {selectedPlatform\n ? tr(\n 'channel.providersAvailableForPlatform',\n `${availableAdapters.length} providers available for ${selectedPlatform.label}`\n )\n : tr('channel.selectPlatformFirst', 'Select a platform first')}\n </p>\n </div>\n\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">\n {tr('channel.status', 'Status')}\n </label>\n <select\n value={formData.status}\n onChange={(e) =>\n setFormData((prev) => ({ ...prev, status: e.target.value as ChannelStatus }))\n }\n className=\"w-full px-3 py-2 text-sm bg-surface border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50 text-text-primary\"\n >\n <option value=\"ACTIVE\">{tr('channel.status.active', 'Active')}</option>\n <option value=\"INACTIVE\">{tr('channel.status.inactive', 'Inactive')}</option>\n </select>\n <p className=\"mt-1 text-[11px] text-text-secondary\">\n {formData.status === 'ACTIVE'\n ? tr('channel.statusHint.active', 'Ready to send immediately')\n : tr('channel.statusHint.inactive', 'Configure before activating')}\n </p>\n </div>\n\n <div>\n <label\n htmlFor=\"create-channel-priority\"\n className=\"block text-sm font-medium text-text-primary mb-1.5\"\n >\n {tr('channel.priority', 'Priority')}\n </label>\n <input\n id=\"create-channel-priority\"\n aria-label={tr('channel.priority', 'Priority')}\n type=\"number\"\n value={formData.priority}\n onChange={(e) =>\n setFormData((prev) => ({\n ...prev,\n priority: parseInt(e.target.value) || 0,\n }))\n }\n min={0}\n max={100}\n className=\"w-full px-3 py-2 text-sm bg-surface border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50\"\n />\n <p className=\"mt-1 text-[11px] text-text-secondary\">\n {tr('channel.priorityHint', 'Lower = higher priority (0-100)')}\n </p>\n </div>\n </div>\n </div>\n </div>\n\n {/* ── Row 2: Platform selection (full width) ─────────────────── */}\n <div className=\"p-5 bg-surface border border-border-subtle rounded-lg space-y-4\">\n <h2 className=\"text-sm font-semibold text-text-primary uppercase tracking-wider\">\n {tr('channel.platform', 'Platform')} <span className=\"text-status-error-text\">*</span>\n </h2>\n\n <div className=\"grid gap-2 grid-cols-2 sm:grid-cols-3 lg:grid-cols-5\">\n {PLATFORM_OPTIONS.map((platform) => {\n const Icon = platform.icon;\n const isSelected = formData.platform === platform.value;\n return (\n <button\n key={platform.value}\n type=\"button\"\n onClick={() => handlePlatformChange(platform.value)}\n className={`relative flex flex-col items-center gap-1.5 p-3 rounded-lg border transition-all text-center ${\n isSelected\n ? `${platform.color} border-current ring-1 ring-current`\n : 'border-border-subtle hover:bg-surface-hover text-text-secondary hover:text-text-primary'\n }`}\n >\n {isSelected && (\n <CheckCircle2 size={14} className=\"absolute top-1.5 right-1.5 text-current\" />\n )}\n <Icon size={20} />\n <span className=\"text-xs font-semibold\">{platform.label}</span>\n <span className=\"text-[10px] leading-tight opacity-70\">\n {platform.description}\n </span>\n </button>\n );\n })}\n </div>\n </div>\n\n {/* ── Row 3: Advanced (collapsible) — webhook config / delivery / retry policy (platformConfig) */}\n <OptionalFields title={tr('channel.advancedOptions', 'Advanced settings')}>\n <div className=\"grid gap-4 lg:grid-cols-2\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">\n {tr('channel.platformConfig', 'Platform Config')}{' '}\n <span className=\"text-[10px] text-text-secondary\">(JSON)</span>\n </label>\n <textarea\n aria-label={tr('channel.platformConfig', 'Platform Config')}\n value={formData.platformConfig}\n onChange={(e) =>\n setFormData((prev) => ({ ...prev, platformConfig: e.target.value }))\n }\n rows={3}\n className=\"w-full px-3 py-2 text-sm bg-surface border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50 resize-none font-mono placeholder:text-text-secondary\"\n placeholder={tr(\n 'channel.platformConfigPlaceholder',\n '{\"fromEmail\": \"noreply@example.com\"}'\n )}\n />\n <p className=\"mt-1 text-[11px] text-text-secondary\">\n {tr('channel.platformConfigHint', 'Platform-specific configuration as JSON')}\n </p>\n </div>\n </div>\n </OptionalFields>\n\n {/* ── Actions ────────────────────────────────────────────────── */}\n <div className=\"flex items-center justify-between pt-2\">\n <button\n type=\"button\"\n onClick={() => navigate('/channels/list')}\n className=\"px-4 py-2 text-sm text-text-secondary hover:text-text-primary hover:bg-surface-hover border border-border-subtle rounded-lg transition-colors\"\n >\n {tr('channel.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n disabled={isSubmitting || !formData.name.trim()}\n className=\"inline-flex items-center gap-2 px-5 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg hover:bg-action-primary-bg-hover rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {isSubmitting ? <Loader2 size={16} className=\"animate-spin\" /> : <Save size={16} />}\n {tr('channel.createChannel', 'Create Channel')}\n </button>\n </div>\n </form>\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CreateChannelPage.js","names":[],"sources":["../../../../../src/channel/modules/channels/pages/CreateChannelPage.tsx"],"sourcesContent":["/**\n * Create Channel Page\n *\n * Form fields match the exact CreateChannelInput from global-channel-svc:\n * name (required), platform (required), organizationId (auto), description,\n * adapterType, status, priority, platformConfig\n *\n * organizationId is injected from shell context — not shown in form.\n * Credentials belong to createConnection mutation — not part of channel creation.\n */\n\nimport { useState } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { GlassCard, OptionalFields } from '@burdenoff/fe-libs/ui';\nimport {\n Radio,\n ArrowLeft,\n Save,\n Loader2,\n AlertCircle,\n Mail,\n Smartphone,\n Bell,\n Inbox,\n Globe,\n Hash,\n Users,\n MessageCircle,\n Send,\n MessageSquare,\n CheckCircle2,\n} from 'lucide-react';\nimport { useChannel } from '../../../providers/ChannelProvider';\nimport {\n useChannels,\n type ChannelPlatform,\n type AdapterType,\n type ChannelStatus,\n type CreateChannelInput,\n} from '../../../hooks/useChannels';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\n\n// ============================================================================\n// PLATFORM CONFIG\n// ============================================================================\n\n// NOTE: AdapterType values MUST match the backend Prisma schema exactly.\n// See: global-channel-svc/prisma/schema/schema.prisma (enum AdapterType)\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-accent-blue bg-accent-blue-subtle border-accent-blue-subtle',\n adapters: [\n { value: 'AWS_SES', label: 'AWS SES' },\n { value: 'AZURE_ACS', label: 'Azure Communication Services' },\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-bg-subtle',\n adapters: [\n { value: 'AWS_SNS', label: 'AWS SNS' },\n { value: 'TWILIO_SMS', label: 'Twilio SMS' },\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-subtle',\n adapters: [{ value: 'FCM', label: 'Firebase Cloud Messaging' }],\n },\n {\n value: 'INAPP',\n label: 'In-App',\n description: 'In-app notifications',\n icon: Inbox,\n color: 'text-accent-indigo bg-accent-indigo-subtle border-accent-indigo-subtle',\n adapters: [{ value: 'INAPP_NATIVE', label: 'Native In-App' }],\n },\n {\n value: 'SLACK',\n label: 'Slack',\n description: 'Workspace notifications',\n icon: Hash,\n color: 'text-accent-pink bg-accent-pink-subtle border-accent-pink-subtle',\n adapters: [{ value: 'SLACK_BOT', label: 'Slack Bot' }],\n },\n {\n value: 'TEAMS',\n label: 'Teams',\n description: 'Teams notifications',\n icon: Users,\n color: 'text-accent-cyan bg-accent-cyan-subtle border-accent-cyan-subtle',\n adapters: [{ value: 'TEAMS_BOT', label: 'Teams Bot' }],\n },\n {\n value: 'DISCORD',\n label: 'Discord',\n description: 'Server notifications',\n icon: MessageCircle,\n color: 'text-accent-violet bg-accent-violet-subtle border-accent-violet-subtle',\n adapters: [{ value: 'DISCORD_BOT', label: 'Discord Bot' }],\n },\n {\n value: 'WHATSAPP',\n label: 'WhatsApp',\n description: 'Business messaging',\n icon: Send,\n color: 'text-accent-emerald bg-accent-emerald-subtle border-accent-emerald-subtle',\n adapters: [\n { value: 'WHATSAPP_CLOUD', label: 'WhatsApp Cloud API' },\n { value: 'WHATSAPP_TWILIO', label: 'WhatsApp via Twilio' },\n ],\n },\n {\n value: 'WEBCHAT',\n label: 'Webchat',\n description: 'Chat widgets',\n icon: MessageSquare,\n color: 'text-accent-teal bg-accent-teal-subtle border-accent-teal-subtle',\n adapters: [], // No WEBCHAT adapters in backend Prisma schema yet\n },\n {\n value: 'WEBHOOK',\n label: 'Webhook',\n description: 'HTTP webhooks',\n icon: Globe,\n color: 'text-accent-orange bg-accent-orange-subtle border-accent-orange-subtle',\n adapters: [{ value: 'WEBHOOK_HTTP', label: 'HTTP Webhook' }],\n },\n];\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\nexport function CreateChannelPage() {\n const navigate = useNavigate();\n const { organizationId } = useChannel();\n const { createChannel } = 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 const [isSubmitting, setIsSubmitting] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n // Form state — matches CreateChannelInput exactly\n const [formData, setFormData] = useState<{\n name: string; // required\n platform: ChannelPlatform; // required\n description: string; // optional\n adapterType: AdapterType | ''; // required (auto-selected based on platform)\n status: ChannelStatus; // optional — default ACTIVE on server\n priority: number; // optional — default 100 on server\n platformConfig: string; // optional — JSON string\n }>({\n name: '',\n platform: 'EMAIL',\n description: '',\n adapterType: 'AWS_SES', // Default to AWS SES for Email platform\n status: 'ACTIVE',\n priority: 100,\n platformConfig: '',\n });\n\n const availableAdapters =\n PLATFORM_OPTIONS.find((p) => p.value === formData.platform)?.adapters || [];\n\n const selectedPlatform = PLATFORM_OPTIONS.find((p) => p.value === formData.platform);\n\n const handlePlatformChange = (platform: ChannelPlatform) => {\n setFormData((prev) => ({ ...prev, platform, adapterType: '' }));\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n\n if (!formData.name.trim()) {\n setError(tr('channel.validation.nameRequired', 'Channel name is required'));\n return;\n }\n\n if (!organizationId) {\n setError(\n tr(\n 'channel.validation.organizationRequired',\n 'Organization context is required. Please ensure an organization is selected.'\n )\n );\n return;\n }\n\n // Validate platformConfig JSON if provided\n let parsedPlatformConfig: Record<string, unknown> | undefined;\n if (formData.platformConfig.trim()) {\n try {\n parsedPlatformConfig = JSON.parse(formData.platformConfig);\n } catch {\n setError(tr('channel.validation.platformConfigJson', 'Platform Config must be valid JSON'));\n return;\n }\n }\n\n setIsSubmitting(true);\n try {\n const input: CreateChannelInput = {\n name: formData.name.trim(),\n platform: formData.platform,\n organizationId,\n description: formData.description.trim() || undefined,\n adapterType: (formData.adapterType as AdapterType) || undefined,\n status: formData.status,\n priority: formData.priority,\n platformConfig: parsedPlatformConfig,\n };\n\n const result = await createChannel(input);\n if (result) {\n navigate(`../${result.id}`);\n } else {\n setError(tr('channel.createFailed', 'Failed to create channel'));\n }\n } catch (err: unknown) {\n setError(\n err instanceof Error ? err.message : tr('channel.createFailed', 'Failed to create channel')\n );\n } finally {\n setIsSubmitting(false);\n }\n };\n\n // ── Render ────────────────────────────────────────────────────────────\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_CREATE}>\n <div className=\"p-3 sm:p-6 space-y-6 max-w-5xl mx-auto\">\n {/* ── Header ───────────────────────────────────────────────────── */}\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => navigate(-1)}\n className=\"p-2 text-text-secondary hover:text-text-primary hover:bg-surface-hover rounded-lg transition-colors\"\n >\n <ArrowLeft size={20} />\n </button>\n <div>\n <h1 className=\"text-xl font-bold text-text-primary flex items-center gap-2\">\n <Radio size={22} className=\"text-accent-primary\" />\n {tr('channel.createChannel', 'Create Channel')}\n </h1>\n <p className=\"text-sm text-text-secondary mt-0.5\">\n {tr('channel.createChannelSubtitle', 'Set up a new communication channel')}\n </p>\n </div>\n </div>\n </div>\n\n {/* ── Error ────────────────────────────────────────────────────── */}\n {error && (\n <div className=\"flex items-center gap-3 p-3 bg-error-500/10 border border-status-error-border/30 rounded-lg\">\n <AlertCircle size={16} className=\"text-status-error-text shrink-0\" />\n <p className=\"text-sm text-error-400\">{error}</p>\n </div>\n )}\n\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n {/* ── Row 1: Name + Description | Status + Provider (2-col) ─── */}\n <div className=\"grid gap-5 lg:grid-cols-2\">\n {/* Left: Name & Description */}\n <GlassCard treatment=\"plain\" className=\"p-5 border-border-seam space-y-4\">\n <h2 className=\"text-sm font-semibold text-text-primary uppercase tracking-wider\">\n {tr('channel.basicInformation', 'Basic Information')}\n </h2>\n\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">\n {tr('channel.name', 'Name')} <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n aria-label={tr('channel.namePlaceholder', 'e.g., Primary Email, SMS Alerts')}\n type=\"text\"\n value={formData.name}\n onChange={(e) => setFormData((prev) => ({ ...prev, name: e.target.value }))}\n className=\"w-full px-3 py-2 text-sm bg-surface border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50 placeholder:text-text-secondary\"\n placeholder={tr('channel.namePlaceholder', 'e.g., Primary Email, SMS Alerts')}\n />\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">\n {tr('channel.description', 'Description')}\n </label>\n <textarea\n aria-label={tr(\n 'channel.descriptionPlaceholder',\n \"Optional description of this channel's purpose\"\n )}\n value={formData.description}\n onChange={(e) =>\n setFormData((prev) => ({ ...prev, description: e.target.value }))\n }\n rows={3}\n className=\"w-full px-3 py-2 text-sm bg-surface border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50 resize-none placeholder:text-text-secondary\"\n placeholder={tr(\n 'channel.descriptionPlaceholder',\n \"Optional description of this channel's purpose\"\n )}\n />\n </div>\n </GlassCard>\n\n {/* Right: Status + Provider / Adapter */}\n <GlassCard treatment=\"plain\" className=\"p-5 border-border-seam space-y-4\">\n <h2 className=\"text-sm font-semibold text-text-primary uppercase tracking-wider\">\n {tr('channel.providerStatus', 'Provider & Status')}\n </h2>\n\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">\n {tr('channel.providerAdapter', 'Provider / Adapter')}\n </label>\n <select\n value={formData.adapterType}\n onChange={(e) =>\n setFormData((prev) => ({\n ...prev,\n adapterType: e.target.value as AdapterType | '',\n }))\n }\n className=\"w-full px-3 py-2 text-sm bg-surface border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50 text-text-primary\"\n >\n <option value=\"\">\n {tr('channel.selectProvider', 'Select a provider (optional)')}\n </option>\n {availableAdapters.map((a) => (\n <option key={a.value} value={a.value}>\n {a.label}\n </option>\n ))}\n </select>\n <p className=\"mt-1 text-[11px] text-text-secondary\">\n {selectedPlatform\n ? tr(\n 'channel.providersAvailableForPlatform',\n `${availableAdapters.length} providers available for ${selectedPlatform.label}`\n )\n : tr('channel.selectPlatformFirst', 'Select a platform first')}\n </p>\n </div>\n\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">\n {tr('channel.status', 'Status')}\n </label>\n <select\n value={formData.status}\n onChange={(e) =>\n setFormData((prev) => ({ ...prev, status: e.target.value as ChannelStatus }))\n }\n className=\"w-full px-3 py-2 text-sm bg-surface border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50 text-text-primary\"\n >\n <option value=\"ACTIVE\">{tr('channel.status.active', 'Active')}</option>\n <option value=\"INACTIVE\">{tr('channel.status.inactive', 'Inactive')}</option>\n </select>\n <p className=\"mt-1 text-[11px] text-text-secondary\">\n {formData.status === 'ACTIVE'\n ? tr('channel.statusHint.active', 'Ready to send immediately')\n : tr('channel.statusHint.inactive', 'Configure before activating')}\n </p>\n </div>\n\n <div>\n <label\n htmlFor=\"create-channel-priority\"\n className=\"block text-sm font-medium text-text-primary mb-1.5\"\n >\n {tr('channel.priority', 'Priority')}\n </label>\n <input\n id=\"create-channel-priority\"\n aria-label={tr('channel.priority', 'Priority')}\n type=\"number\"\n value={formData.priority}\n onChange={(e) =>\n setFormData((prev) => ({\n ...prev,\n priority: parseInt(e.target.value) || 0,\n }))\n }\n min={0}\n max={100}\n className=\"w-full px-3 py-2 text-sm bg-surface border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50\"\n />\n <p className=\"mt-1 text-[11px] text-text-secondary\">\n {tr('channel.priorityHint', 'Lower = higher priority (0-100)')}\n </p>\n </div>\n </div>\n </GlassCard>\n </div>\n\n {/* ── Row 2: Platform selection (full width) ─────────────────── */}\n <GlassCard treatment=\"plain\" className=\"p-5 border-border-seam space-y-4\">\n <h2 className=\"text-sm font-semibold text-text-primary uppercase tracking-wider\">\n {tr('channel.platform', 'Platform')} <span className=\"text-status-error-text\">*</span>\n </h2>\n\n <div className=\"grid gap-2 grid-cols-2 sm:grid-cols-3 lg:grid-cols-5\">\n {PLATFORM_OPTIONS.map((platform) => {\n const Icon = platform.icon;\n const isSelected = formData.platform === platform.value;\n return (\n <button\n key={platform.value}\n type=\"button\"\n onClick={() => handlePlatformChange(platform.value)}\n className={`relative flex flex-col items-center gap-1.5 p-3 rounded-lg border transition-all text-center ${\n isSelected\n ? `${platform.color} border-current ring-1 ring-current`\n : 'border-border-subtle hover:bg-surface-hover text-text-secondary hover:text-text-primary'\n }`}\n >\n {isSelected && (\n <CheckCircle2 size={14} className=\"absolute top-1.5 right-1.5 text-current\" />\n )}\n <Icon size={20} />\n <span className=\"text-xs font-semibold\">{platform.label}</span>\n <span className=\"text-[10px] leading-tight opacity-70\">\n {platform.description}\n </span>\n </button>\n );\n })}\n </div>\n </GlassCard>\n\n {/* ── Row 3: Advanced (collapsible) — webhook config / delivery / retry policy (platformConfig) */}\n <OptionalFields title={tr('channel.advancedOptions', 'Advanced settings')}>\n <div className=\"grid gap-4 lg:grid-cols-2\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1.5\">\n {tr('channel.platformConfig', 'Platform Config')}{' '}\n <span className=\"text-[10px] text-text-secondary\">(JSON)</span>\n </label>\n <textarea\n aria-label={tr('channel.platformConfig', 'Platform Config')}\n value={formData.platformConfig}\n onChange={(e) =>\n setFormData((prev) => ({ ...prev, platformConfig: e.target.value }))\n }\n rows={3}\n className=\"w-full px-3 py-2 text-sm bg-surface border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg/50 resize-none font-mono placeholder:text-text-secondary\"\n placeholder={tr(\n 'channel.platformConfigPlaceholder',\n '{\"fromEmail\": \"noreply@example.com\"}'\n )}\n />\n <p className=\"mt-1 text-[11px] text-text-secondary\">\n {tr('channel.platformConfigHint', 'Platform-specific configuration as JSON')}\n </p>\n </div>\n </div>\n </OptionalFields>\n\n {/* ── Actions ────────────────────────────────────────────────── */}\n <div className=\"flex items-center justify-between pt-2\">\n <button\n type=\"button\"\n onClick={() => navigate('/channels/list')}\n className=\"px-4 py-2 text-sm text-text-secondary hover:text-text-primary hover:bg-surface-hover border border-border-subtle rounded-lg transition-colors\"\n >\n {tr('channel.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n disabled={isSubmitting || !formData.name.trim()}\n className=\"inline-flex items-center gap-2 px-5 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg hover:bg-action-primary-bg-hover rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {isSubmitting ? <Loader2 size={16} className=\"animate-spin\" /> : <Save size={16} />}\n {tr('channel.createChannel', 'Create Channel')}\n </button>\n </div>\n </form>\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
|
|
@@ -7,8 +7,9 @@ import { useI18n as s } from "@burdenoff/fe-libs/shared/providers/shell/I18nProv
|
|
|
7
7
|
import { AlertCircle as c, ArrowLeft as l, Loader2 as u, Save as d, Shield as f } from "lucide-react";
|
|
8
8
|
import { useNavigate as p, useSearchParams as m } from "react-router-dom";
|
|
9
9
|
import { Fragment as h, jsx as g, jsxs as _ } from "react/jsx-runtime";
|
|
10
|
+
import { GlassCard as v } from "@burdenoff/fe-libs/ui";
|
|
10
11
|
//#region src/channel/modules/channels/pages/CreateConnectionPage.tsx
|
|
11
|
-
var
|
|
12
|
+
var y = [
|
|
12
13
|
{
|
|
13
14
|
value: "OAUTH2",
|
|
14
15
|
label: "OAuth 2.0",
|
|
@@ -35,11 +36,11 @@ var v = [
|
|
|
35
36
|
help: "Mail server credentials"
|
|
36
37
|
}
|
|
37
38
|
];
|
|
38
|
-
function
|
|
39
|
-
let
|
|
40
|
-
let n =
|
|
39
|
+
function b() {
|
|
40
|
+
let b = p(), [x] = m(), { organizationId: S } = e(), { channels: C, isLoading: w } = n(), { t: T } = s(), E = (e, t) => {
|
|
41
|
+
let n = T(e);
|
|
41
42
|
return n === e ? t : n;
|
|
42
|
-
}, [
|
|
43
|
+
}, [D, O] = o(x.get("channelId") ?? ""), { createConnection: k } = t(D), [A, j] = o("OAUTH2"), [M, N] = o(""), [P, F] = o(""), [I, L] = o(""), [R, z] = o(""), [B, V] = o(!1), [H, U] = o(null), W = a(() => C.find((e) => e.id === D) ?? null, [C, D]);
|
|
43
44
|
return /* @__PURE__ */ g(r, {
|
|
44
45
|
permission: i.CHANNEL_CONNECTION_CREATE,
|
|
45
46
|
children: /* @__PURE__ */ _("div", {
|
|
@@ -47,184 +48,188 @@ function y() {
|
|
|
47
48
|
children: [
|
|
48
49
|
/* @__PURE__ */ _("button", {
|
|
49
50
|
type: "button",
|
|
50
|
-
onClick: () =>
|
|
51
|
+
onClick: () => b(-1),
|
|
51
52
|
className: "flex items-center gap-2 text-fg-muted hover:text-fg mb-6",
|
|
52
|
-
children: [/* @__PURE__ */ g(l, { className: "size-4 " }),
|
|
53
|
+
children: [/* @__PURE__ */ g(l, { className: "size-4 " }), E("common.back", "Back")]
|
|
53
54
|
}),
|
|
54
55
|
/* @__PURE__ */ _("div", {
|
|
55
56
|
className: "flex items-center gap-3 mb-6",
|
|
56
57
|
children: [/* @__PURE__ */ g(f, { className: "size-7 text-accent-blue" }), /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("h1", {
|
|
57
58
|
className: "text-2xl font-bold text-fg",
|
|
58
|
-
children:
|
|
59
|
+
children: E("connection.create.title", "Create Connection")
|
|
59
60
|
}), /* @__PURE__ */ g("p", {
|
|
60
61
|
className: "text-sm text-fg-muted",
|
|
61
|
-
children:
|
|
62
|
+
children: E("connection.create.subtitle", "Authenticate a channel against its platform provider.")
|
|
62
63
|
})] })]
|
|
63
64
|
}),
|
|
64
|
-
|
|
65
|
+
H && /* @__PURE__ */ _("div", {
|
|
65
66
|
className: "bg-status-error-bg-subtle border border-status-error-bg-subtle rounded-lg p-4 mb-6 flex gap-3",
|
|
66
67
|
children: [/* @__PURE__ */ g(c, { className: "size-5 text-status-error-text shrink-0" }), /* @__PURE__ */ g("p", {
|
|
67
68
|
className: "text-sm text-status-error-text",
|
|
68
|
-
children:
|
|
69
|
+
children: H
|
|
69
70
|
})]
|
|
70
71
|
}),
|
|
71
|
-
/* @__PURE__ */
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
72
|
+
/* @__PURE__ */ g(v, {
|
|
73
|
+
treatment: "plain",
|
|
74
|
+
className: "border-border-seam p-6",
|
|
75
|
+
children: /* @__PURE__ */ _("form", {
|
|
76
|
+
onSubmit: async (e) => {
|
|
77
|
+
if (e.preventDefault(), U(null), !D) {
|
|
78
|
+
U(E("connection.validation.channelRequired", "Pick a channel to connect"));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (!S) {
|
|
82
|
+
U(E("connection.validation.organizationRequired", "Organization context missing"));
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
let t;
|
|
86
|
+
if (R.trim()) try {
|
|
87
|
+
t = JSON.parse(R);
|
|
88
|
+
} catch {
|
|
89
|
+
U(E("connection.validation.credentialsJson", "Credentials must be valid JSON"));
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
V(!0);
|
|
93
|
+
try {
|
|
94
|
+
let e = await k({
|
|
95
|
+
authType: A,
|
|
96
|
+
authCode: M.trim() || void 0,
|
|
97
|
+
apiKey: P.trim() || void 0,
|
|
98
|
+
redirectUri: I.trim() || void 0,
|
|
99
|
+
credentials: t
|
|
100
|
+
});
|
|
101
|
+
e ? b(`../${e.id}`) : U(E("connection.createFailed", "Failed to create connection"));
|
|
102
|
+
} catch (e) {
|
|
103
|
+
U(e instanceof Error ? e.message : E("connection.createFailed", "Failed to create connection"));
|
|
104
|
+
} finally {
|
|
105
|
+
V(!1);
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
className: "space-y-6",
|
|
109
|
+
children: [
|
|
110
|
+
/* @__PURE__ */ _("div", { children: [
|
|
111
|
+
/* @__PURE__ */ _("label", {
|
|
112
|
+
className: "block text-sm font-medium text-fg mb-2",
|
|
113
|
+
children: [E("connection.field.channel", "Channel"), " *"]
|
|
114
|
+
}),
|
|
115
|
+
/* @__PURE__ */ _("select", {
|
|
116
|
+
value: D,
|
|
117
|
+
onChange: (e) => O(e.target.value),
|
|
118
|
+
className: "w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg",
|
|
119
|
+
required: !0,
|
|
120
|
+
disabled: w,
|
|
121
|
+
children: [/* @__PURE__ */ g("option", {
|
|
122
|
+
value: "",
|
|
123
|
+
children: w ? E("common.loading", "Loading...") : E("connection.field.channelPlaceholder", "Select a channel")
|
|
124
|
+
}), C.map((e) => /* @__PURE__ */ _("option", {
|
|
125
|
+
value: e.id,
|
|
126
|
+
children: [
|
|
127
|
+
e.name,
|
|
128
|
+
" (",
|
|
129
|
+
e.platform,
|
|
130
|
+
")"
|
|
131
|
+
]
|
|
132
|
+
}, e.id))]
|
|
133
|
+
}),
|
|
134
|
+
W && /* @__PURE__ */ _("p", {
|
|
135
|
+
className: "mt-1 text-xs text-fg-muted",
|
|
136
|
+
children: [E("connection.field.channelHint", "Adapter: "), W.adapterType ?? "—"]
|
|
137
|
+
})
|
|
138
|
+
] }),
|
|
139
|
+
/* @__PURE__ */ _("div", { children: [/* @__PURE__ */ _("label", {
|
|
108
140
|
className: "block text-sm font-medium text-fg mb-2",
|
|
109
|
-
children: [
|
|
110
|
-
}),
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
onChange: (e) => D(e.target.value),
|
|
141
|
+
children: [E("connection.field.authType", "Authentication Type"), " *"]
|
|
142
|
+
}), /* @__PURE__ */ g("select", {
|
|
143
|
+
value: A,
|
|
144
|
+
onChange: (e) => j(e.target.value),
|
|
114
145
|
className: "w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg",
|
|
115
146
|
required: !0,
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
value: "",
|
|
119
|
-
children: C ? T("common.loading", "Loading...") : T("connection.field.channelPlaceholder", "Select a channel")
|
|
120
|
-
}), S.map((e) => /* @__PURE__ */ _("option", {
|
|
121
|
-
value: e.id,
|
|
147
|
+
children: y.map((e) => /* @__PURE__ */ _("option", {
|
|
148
|
+
value: e.value,
|
|
122
149
|
children: [
|
|
123
|
-
e.
|
|
124
|
-
"
|
|
125
|
-
e.
|
|
126
|
-
")"
|
|
150
|
+
e.label,
|
|
151
|
+
": ",
|
|
152
|
+
e.help
|
|
127
153
|
]
|
|
128
|
-
}, e.
|
|
129
|
-
}),
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
value: e.value,
|
|
145
|
-
children: [
|
|
146
|
-
e.label,
|
|
147
|
-
": ",
|
|
148
|
-
e.help
|
|
149
|
-
]
|
|
150
|
-
}, e.value))
|
|
151
|
-
})] }),
|
|
152
|
-
k === "OAUTH2" && /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
|
|
153
|
-
htmlFor: "create-conn-auth-code",
|
|
154
|
-
className: "block text-sm font-medium text-fg mb-2",
|
|
155
|
-
children: T("connection.field.authCode", "OAuth Authorization Code")
|
|
156
|
-
}), /* @__PURE__ */ g("input", {
|
|
157
|
-
id: "create-conn-auth-code",
|
|
158
|
-
"aria-label": T("connection.field.authCode", "OAuth Authorization Code"),
|
|
159
|
-
type: "text",
|
|
160
|
-
value: j,
|
|
161
|
-
onChange: (e) => M(e.target.value),
|
|
162
|
-
className: "w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg",
|
|
163
|
-
autoComplete: "off"
|
|
164
|
-
})] }), /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
|
|
165
|
-
htmlFor: "create-conn-redirect-uri",
|
|
166
|
-
className: "block text-sm font-medium text-fg mb-2",
|
|
167
|
-
children: T("connection.field.redirectUri", "Redirect URI")
|
|
168
|
-
}), /* @__PURE__ */ g("input", {
|
|
169
|
-
id: "create-conn-redirect-uri",
|
|
170
|
-
"aria-label": T("connection.field.redirectUri", "Redirect URI"),
|
|
171
|
-
type: "url",
|
|
172
|
-
value: F,
|
|
173
|
-
onChange: (e) => I(e.target.value),
|
|
174
|
-
className: "w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg"
|
|
175
|
-
})] })] }),
|
|
176
|
-
(k === "API_KEY" || k === "BOT_TOKEN") && /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
|
|
177
|
-
htmlFor: "create-conn-api-key",
|
|
178
|
-
className: "block text-sm font-medium text-fg mb-2",
|
|
179
|
-
children: T("connection.field.apiKey", "API Key / Token")
|
|
180
|
-
}), /* @__PURE__ */ g("input", {
|
|
181
|
-
id: "create-conn-api-key",
|
|
182
|
-
"aria-label": T("connection.field.apiKey", "API Key / Token"),
|
|
183
|
-
type: "password",
|
|
184
|
-
value: N,
|
|
185
|
-
onChange: (e) => P(e.target.value),
|
|
186
|
-
className: "w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg",
|
|
187
|
-
autoComplete: "new-password"
|
|
188
|
-
})] }),
|
|
189
|
-
/* @__PURE__ */ _("div", { children: [
|
|
190
|
-
/* @__PURE__ */ g("label", {
|
|
154
|
+
}, e.value))
|
|
155
|
+
})] }),
|
|
156
|
+
A === "OAUTH2" && /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
|
|
157
|
+
htmlFor: "create-conn-auth-code",
|
|
158
|
+
className: "block text-sm font-medium text-fg mb-2",
|
|
159
|
+
children: E("connection.field.authCode", "OAuth Authorization Code")
|
|
160
|
+
}), /* @__PURE__ */ g("input", {
|
|
161
|
+
id: "create-conn-auth-code",
|
|
162
|
+
"aria-label": E("connection.field.authCode", "OAuth Authorization Code"),
|
|
163
|
+
type: "text",
|
|
164
|
+
value: M,
|
|
165
|
+
onChange: (e) => N(e.target.value),
|
|
166
|
+
className: "w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg",
|
|
167
|
+
autoComplete: "off"
|
|
168
|
+
})] }), /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
|
|
169
|
+
htmlFor: "create-conn-redirect-uri",
|
|
191
170
|
className: "block text-sm font-medium text-fg mb-2",
|
|
192
|
-
children:
|
|
193
|
-
}),
|
|
194
|
-
|
|
195
|
-
"aria-label": "
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
className: "w-full px-3 py-2 bg-bg border border-border-subtle rounded-md
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
className: "
|
|
204
|
-
children:
|
|
171
|
+
children: E("connection.field.redirectUri", "Redirect URI")
|
|
172
|
+
}), /* @__PURE__ */ g("input", {
|
|
173
|
+
id: "create-conn-redirect-uri",
|
|
174
|
+
"aria-label": E("connection.field.redirectUri", "Redirect URI"),
|
|
175
|
+
type: "url",
|
|
176
|
+
value: I,
|
|
177
|
+
onChange: (e) => L(e.target.value),
|
|
178
|
+
className: "w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg"
|
|
179
|
+
})] })] }),
|
|
180
|
+
(A === "API_KEY" || A === "BOT_TOKEN") && /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
|
|
181
|
+
htmlFor: "create-conn-api-key",
|
|
182
|
+
className: "block text-sm font-medium text-fg mb-2",
|
|
183
|
+
children: E("connection.field.apiKey", "API Key / Token")
|
|
184
|
+
}), /* @__PURE__ */ g("input", {
|
|
185
|
+
id: "create-conn-api-key",
|
|
186
|
+
"aria-label": E("connection.field.apiKey", "API Key / Token"),
|
|
187
|
+
type: "password",
|
|
188
|
+
value: P,
|
|
189
|
+
onChange: (e) => F(e.target.value),
|
|
190
|
+
className: "w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg",
|
|
191
|
+
autoComplete: "new-password"
|
|
192
|
+
})] }),
|
|
193
|
+
/* @__PURE__ */ _("div", { children: [
|
|
194
|
+
/* @__PURE__ */ g("label", {
|
|
195
|
+
className: "block text-sm font-medium text-fg mb-2",
|
|
196
|
+
children: E("connection.field.credentials", "Additional Credentials (JSON)")
|
|
197
|
+
}),
|
|
198
|
+
/* @__PURE__ */ g("textarea", {
|
|
199
|
+
"aria-label": "{\"smtpHost\": \"smtp.example.com\"}",
|
|
200
|
+
value: R,
|
|
201
|
+
onChange: (e) => z(e.target.value),
|
|
202
|
+
rows: 6,
|
|
203
|
+
className: "w-full px-3 py-2 bg-bg border border-border-subtle rounded-md font-mono text-sm text-fg",
|
|
204
|
+
placeholder: "{\"smtpHost\": \"smtp.example.com\"}"
|
|
205
|
+
}),
|
|
206
|
+
/* @__PURE__ */ g("p", {
|
|
207
|
+
className: "mt-1 text-xs text-fg-muted",
|
|
208
|
+
children: E("connection.field.credentialsHint", "Encrypted at rest with AES-256-GCM. Never logged in plaintext.")
|
|
209
|
+
})
|
|
210
|
+
] }),
|
|
211
|
+
/* @__PURE__ */ _("div", {
|
|
212
|
+
className: "flex gap-3 pt-2",
|
|
213
|
+
children: [/* @__PURE__ */ _("button", {
|
|
214
|
+
type: "submit",
|
|
215
|
+
disabled: B || !D,
|
|
216
|
+
className: "flex items-center gap-2 px-4 py-2 bg-accent-blue text-fg-on-accent rounded-md disabled:opacity-50",
|
|
217
|
+
children: [B ? /* @__PURE__ */ g(u, { className: "size-4 animate-spin" }) : /* @__PURE__ */ g(d, { className: "size-4 " }), E("connection.create.submit", "Create Connection")]
|
|
218
|
+
}), /* @__PURE__ */ g("button", {
|
|
219
|
+
type: "button",
|
|
220
|
+
onClick: () => b(-1),
|
|
221
|
+
className: "px-4 py-2 text-fg border border-border-subtle rounded-md",
|
|
222
|
+
children: E("common.cancel", "Cancel")
|
|
223
|
+
})]
|
|
205
224
|
})
|
|
206
|
-
]
|
|
207
|
-
|
|
208
|
-
className: "flex gap-3 pt-2",
|
|
209
|
-
children: [/* @__PURE__ */ _("button", {
|
|
210
|
-
type: "submit",
|
|
211
|
-
disabled: z || !E,
|
|
212
|
-
className: "flex items-center gap-2 px-4 py-2 bg-accent-blue text-fg-on-accent rounded-md disabled:opacity-50",
|
|
213
|
-
children: [z ? /* @__PURE__ */ g(u, { className: "size-4 animate-spin" }) : /* @__PURE__ */ g(d, { className: "size-4 " }), T("connection.create.submit", "Create Connection")]
|
|
214
|
-
}), /* @__PURE__ */ g("button", {
|
|
215
|
-
type: "button",
|
|
216
|
-
onClick: () => y(-1),
|
|
217
|
-
className: "px-4 py-2 text-fg border border-border-subtle rounded-md",
|
|
218
|
-
children: T("common.cancel", "Cancel")
|
|
219
|
-
})]
|
|
220
|
-
})
|
|
221
|
-
]
|
|
225
|
+
]
|
|
226
|
+
})
|
|
222
227
|
})
|
|
223
228
|
]
|
|
224
229
|
})
|
|
225
230
|
});
|
|
226
231
|
}
|
|
227
232
|
//#endregion
|
|
228
|
-
export {
|
|
233
|
+
export { b as CreateConnectionPage };
|
|
229
234
|
|
|
230
235
|
//# sourceMappingURL=CreateConnectionPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CreateConnectionPage.js","names":[],"sources":["../../../../../src/channel/modules/channels/pages/CreateConnectionPage.tsx"],"sourcesContent":["/**\n * Create Connection Page\n *\n * Standalone page mounted at /channels/connections/create.\n * User picks a target channel, then provides auth details.\n *\n * Form fields match CreateConnectionInput exactly (less channelId which is\n * resolved from the selector).\n */\n\nimport { useMemo, useState } from 'react';\nimport { useNavigate, useSearchParams } 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 { useChannel, useChannels, type ChannelAuthType } from '../../../hooks/useChannels';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\n\nconst AUTH_TYPES: { value: ChannelAuthType; label: string; help: string }[] = [\n { value: 'OAUTH2', label: 'OAuth 2.0', help: 'Authorization code flow' },\n { value: 'API_KEY', label: 'API Key', help: 'Static API key' },\n { value: 'BOT_TOKEN', label: 'Bot Token', help: 'Slack/Discord/Teams bot token' },\n { value: 'WEBHOOK', label: 'Webhook', help: 'Shared secret for HMAC verification' },\n { value: 'SMTP_IMAP', label: 'SMTP / IMAP', help: 'Mail server credentials' },\n];\n\nexport function CreateConnectionPage() {\n const navigate = useNavigate();\n const [searchParams] = useSearchParams();\n const { organizationId } = useChannelContext();\n const { channels, isLoading: channelsLoading } = 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 const initialChannelId = searchParams.get('channelId') ?? '';\n const [selectedChannelId, setSelectedChannelId] = useState<string>(initialChannelId);\n const { createConnection } = useChannel(selectedChannelId);\n\n const [authType, setAuthType] = useState<ChannelAuthType>('OAUTH2');\n const [authCode, setAuthCode] = useState('');\n const [apiKey, setApiKey] = useState('');\n const [redirectUri, setRedirectUri] = useState('');\n const [credentials, setCredentials] = useState('');\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const selectedChannel = useMemo(\n () => channels.find((c) => c.id === selectedChannelId) ?? null,\n [channels, selectedChannelId]\n );\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n\n if (!selectedChannelId) {\n setError(tr('connection.validation.channelRequired', 'Pick a channel to connect'));\n return;\n }\n if (!organizationId) {\n setError(tr('connection.validation.organizationRequired', 'Organization context missing'));\n return;\n }\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 setIsSubmitting(true);\n try {\n const result = await createConnection({\n authType,\n authCode: authCode.trim() || undefined,\n apiKey: apiKey.trim() || undefined,\n redirectUri: redirectUri.trim() || undefined,\n credentials: parsedCredentials,\n });\n if (result) {\n navigate(`../${result.id}`);\n } else {\n setError(tr('connection.createFailed', 'Failed to create connection'));\n }\n } catch (err: unknown) {\n setError(\n err instanceof Error\n ? err.message\n : tr('connection.createFailed', 'Failed to create connection')\n );\n } finally {\n setIsSubmitting(false);\n }\n };\n\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_CONNECTION_CREATE}>\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.create.title', 'Create Connection')}\n </h1>\n <p className=\"text-sm text-fg-muted\">\n {tr(\n 'connection.create.subtitle',\n 'Authenticate a channel against its platform provider.'\n )}\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>\n <label className=\"block text-sm font-medium text-fg mb-2\">\n {tr('connection.field.channel', 'Channel')} *\n </label>\n <select\n value={selectedChannelId}\n onChange={(e) => setSelectedChannelId(e.target.value)}\n className=\"w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg\"\n required\n disabled={channelsLoading}\n >\n <option value=\"\">\n {channelsLoading\n ? tr('common.loading', 'Loading...')\n : tr('connection.field.channelPlaceholder', 'Select a channel')}\n </option>\n {channels.map((c) => (\n <option key={c.id} value={c.id}>\n {c.name} ({c.platform})\n </option>\n ))}\n </select>\n {selectedChannel && (\n <p className=\"mt-1 text-xs text-fg-muted\">\n {tr('connection.field.channelHint', 'Adapter: ')}\n {selectedChannel.adapterType ?? '—'}\n </p>\n )}\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-fg mb-2\">\n {tr('connection.field.authType', 'Authentication Type')} *\n </label>\n <select\n value={authType}\n onChange={(e) => setAuthType(e.target.value as ChannelAuthType)}\n className=\"w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg\"\n required\n >\n {AUTH_TYPES.map((a) => (\n <option key={a.value} value={a.value}>\n {a.label}: {a.help}\n </option>\n ))}\n </select>\n </div>\n\n {authType === 'OAUTH2' && (\n <>\n <div>\n <label\n htmlFor=\"create-conn-auth-code\"\n className=\"block text-sm font-medium text-fg mb-2\"\n >\n {tr('connection.field.authCode', 'OAuth Authorization Code')}\n </label>\n <input\n id=\"create-conn-auth-code\"\n aria-label={tr('connection.field.authCode', 'OAuth Authorization Code')}\n type=\"text\"\n value={authCode}\n onChange={(e) => setAuthCode(e.target.value)}\n className=\"w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg\"\n autoComplete=\"off\"\n />\n </div>\n <div>\n <label\n htmlFor=\"create-conn-redirect-uri\"\n className=\"block text-sm font-medium text-fg mb-2\"\n >\n {tr('connection.field.redirectUri', 'Redirect URI')}\n </label>\n <input\n id=\"create-conn-redirect-uri\"\n aria-label={tr('connection.field.redirectUri', 'Redirect URI')}\n type=\"url\"\n value={redirectUri}\n onChange={(e) => setRedirectUri(e.target.value)}\n className=\"w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg\"\n />\n </div>\n </>\n )}\n\n {(authType === 'API_KEY' || authType === 'BOT_TOKEN') && (\n <div>\n <label\n htmlFor=\"create-conn-api-key\"\n className=\"block text-sm font-medium text-fg mb-2\"\n >\n {tr('connection.field.apiKey', 'API Key / Token')}\n </label>\n <input\n id=\"create-conn-api-key\"\n aria-label={tr('connection.field.apiKey', 'API Key / Token')}\n type=\"password\"\n value={apiKey}\n onChange={(e) => setApiKey(e.target.value)}\n className=\"w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg\"\n autoComplete=\"new-password\"\n />\n </div>\n )}\n\n <div>\n <label className=\"block text-sm font-medium text-fg mb-2\">\n {tr('connection.field.credentials', 'Additional Credentials (JSON)')}\n </label>\n <textarea\n aria-label='{\"smtpHost\": \"smtp.example.com\"}'\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='{\"smtpHost\": \"smtp.example.com\"}'\n />\n <p className=\"mt-1 text-xs text-fg-muted\">\n {tr(\n 'connection.field.credentialsHint',\n 'Encrypted at rest with AES-256-GCM. Never logged in plaintext.'\n )}\n </p>\n </div>\n\n <div className=\"flex gap-3 pt-2\">\n <button\n type=\"submit\"\n disabled={isSubmitting || !selectedChannelId}\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('connection.create.submit', 'Create Connection')}\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":";;;;;;;;;;AAmBA,IAAM,IAAwE;CAC5E;EAAE,OAAO;EAAU,OAAO;EAAa,MAAM;EAA2B;CACxE;EAAE,OAAO;EAAW,OAAO;EAAW,MAAM;EAAkB;CAC9D;EAAE,OAAO;EAAa,OAAO;EAAa,MAAM;EAAiC;CACjF;EAAE,OAAO;EAAW,OAAO;EAAW,MAAM;EAAuC;CACnF;EAAE,OAAO;EAAa,OAAO;EAAe,MAAM;EAA2B;CAC9E;AAED,SAAgB,IAAuB;CACrC,IAAM,IAAW,GAAa,EACxB,CAAC,KAAgB,GAAiB,EAClC,EAAE,sBAAmB,GAAmB,EACxC,EAAE,aAAU,WAAW,MAAoB,GAAa,EACxD,EAAE,SAAM,GAAS,EACjB,KAAM,GAAa,MAA6B;EACpD,IAAM,IAAQ,EAAE,EAAI;AACpB,SAAO,MAAU,IAAM,IAAW;IAI9B,CAAC,GAAmB,KAAwB,EADzB,EAAa,IAAI,YAAY,IAAI,GAC0B,EAC9E,EAAE,wBAAqB,EAAW,EAAkB,EAEpD,CAAC,GAAU,KAAe,EAA0B,SAAS,EAC7D,CAAC,GAAU,KAAe,EAAS,GAAG,EACtC,CAAC,GAAQ,KAAa,EAAS,GAAG,EAClC,CAAC,GAAa,KAAkB,EAAS,GAAG,EAC5C,CAAC,GAAa,KAAkB,EAAS,GAAG,EAC5C,CAAC,GAAc,KAAmB,EAAS,GAAM,EACjD,CAAC,GAAO,KAAY,EAAwB,KAAK,EAEjD,IAAkB,QAChB,EAAS,MAAM,MAAM,EAAE,OAAO,EAAkB,IAAI,MAC1D,CAAC,GAAU,EAAkB,CAC9B;AAkDD,QACE,kBAAC,GAAD;EAAiB,YAAY,EAAoB;YAC/C,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAS,GAAG;KAC3B,WAAU;eAHZ,CAKE,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA,EAChC,EAAG,eAAe,OAAO,CACnB;;IAET,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAQ,WAAU,2BAA4B,CAAA,EAC9C,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAG,2BAA2B,oBAAoB;MAChD,CAAA,EACL,kBAAC,KAAD;MAAG,WAAU;gBACV,EACC,8BACA,wDACD;MACC,CAAA,CACA,EAAA,CAAA,CACF;;IAEL,KACC,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAa,WAAU,0CAA2C,CAAA,EAClE,kBAAC,KAAD;MAAG,WAAU;gBAAkC;MAAU,CAAA,CACrD;;IAGR,kBAAC,QAAD;KACE,UAnFa,OAAO,MAAuB;AAIjD,UAHA,EAAE,gBAAgB,EAClB,EAAS,KAAK,EAEV,CAAC,GAAmB;AACtB,SAAS,EAAG,yCAAyC,4BAA4B,CAAC;AAClF;;AAEF,UAAI,CAAC,GAAgB;AACnB,SAAS,EAAG,8CAA8C,+BAA+B,CAAC;AAC1F;;MAGF,IAAI;AACJ,UAAI,EAAY,MAAM,CACpB,KAAI;AACF,WAAoB,KAAK,MAAM,EAAY;cACrC;AACN,SAAS,EAAG,yCAAyC,iCAAiC,CAAC;AACvF;;AAIJ,QAAgB,GAAK;AACrB,UAAI;OACF,IAAM,IAAS,MAAM,EAAiB;QACpC;QACA,UAAU,EAAS,MAAM,IAAI,KAAA;QAC7B,QAAQ,EAAO,MAAM,IAAI,KAAA;QACzB,aAAa,EAAY,MAAM,IAAI,KAAA;QACnC,aAAa;QACd,CAAC;AACF,OAAI,IACF,EAAS,MAAM,EAAO,KAAK,GAE3B,EAAS,EAAG,2BAA2B,8BAA8B,CAAC;eAEjE,GAAc;AACrB,SACE,aAAe,QACX,EAAI,UACJ,EAAG,2BAA2B,8BAA8B,CACjE;gBACO;AACR,SAAgB,GAAM;;;KAwClB,WAAU;eAFZ;MAIE,kBAAC,OAAD,EAAA,UAAA;OACE,kBAAC,SAAD;QAAO,WAAU;kBAAjB,CACG,EAAG,4BAA4B,UAAU,EAAC,KACrC;;OACR,kBAAC,UAAD;QACE,OAAO;QACP,WAAW,MAAM,EAAqB,EAAE,OAAO,MAAM;QACrD,WAAU;QACV,UAAA;QACA,UAAU;kBALZ,CAOE,kBAAC,UAAD;SAAQ,OAAM;mBACX,IACG,EAAG,kBAAkB,aAAa,GAClC,EAAG,uCAAuC,mBAAmB;SAC1D,CAAA,EACR,EAAS,KAAK,MACb,kBAAC,UAAD;SAAmB,OAAO,EAAE;mBAA5B;UACG,EAAE;UAAK;UAAG,EAAE;UAAS;UACf;WAFI,EAAE,GAEN,CACT,CACK;;OACR,KACC,kBAAC,KAAD;QAAG,WAAU;kBAAb,CACG,EAAG,gCAAgC,YAAY,EAC/C,EAAgB,eAAe,IAC9B;;OAEF,EAAA,CAAA;MAEN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,WAAU;iBAAjB,CACG,EAAG,6BAA6B,sBAAsB,EAAC,KAClD;UACR,kBAAC,UAAD;OACE,OAAO;OACP,WAAW,MAAM,EAAY,EAAE,OAAO,MAAyB;OAC/D,WAAU;OACV,UAAA;iBAEC,EAAW,KAAK,MACf,kBAAC,UAAD;QAAsB,OAAO,EAAE;kBAA/B;SACG,EAAE;SAAM;SAAG,EAAE;SACP;UAFI,EAAE,MAEN,CACT;OACK,CAAA,CACL,EAAA,CAAA;MAEL,MAAa,YACZ,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OACE,SAAQ;OACR,WAAU;iBAET,EAAG,6BAA6B,2BAA2B;OACtD,CAAA,EACR,kBAAC,SAAD;OACE,IAAG;OACH,cAAY,EAAG,6BAA6B,2BAA2B;OACvE,MAAK;OACL,OAAO;OACP,WAAW,MAAM,EAAY,EAAE,OAAO,MAAM;OAC5C,WAAU;OACV,cAAa;OACb,CAAA,CACE,EAAA,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OACE,SAAQ;OACR,WAAU;iBAET,EAAG,gCAAgC,eAAe;OAC7C,CAAA,EACR,kBAAC,SAAD;OACE,IAAG;OACH,cAAY,EAAG,gCAAgC,eAAe;OAC9D,MAAK;OACL,OAAO;OACP,WAAW,MAAM,EAAe,EAAE,OAAO,MAAM;OAC/C,WAAU;OACV,CAAA,CACE,EAAA,CAAA,CACL,EAAA,CAAA;OAGH,MAAa,aAAa,MAAa,gBACvC,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OACE,SAAQ;OACR,WAAU;iBAET,EAAG,2BAA2B,kBAAkB;OAC3C,CAAA,EACR,kBAAC,SAAD;OACE,IAAG;OACH,cAAY,EAAG,2BAA2B,kBAAkB;OAC5D,MAAK;OACL,OAAO;OACP,WAAW,MAAM,EAAU,EAAE,OAAO,MAAM;OAC1C,WAAU;OACV,cAAa;OACb,CAAA,CACE,EAAA,CAAA;MAGR,kBAAC,OAAD,EAAA,UAAA;OACE,kBAAC,SAAD;QAAO,WAAU;kBACd,EAAG,gCAAgC,gCAAgC;QAC9D,CAAA;OACR,kBAAC,YAAD;QACE,cAAW;QACX,OAAO;QACP,WAAW,MAAM,EAAe,EAAE,OAAO,MAAM;QAC/C,MAAM;QACN,WAAU;QACV,aAAY;QACZ,CAAA;OACF,kBAAC,KAAD;QAAG,WAAU;kBACV,EACC,oCACA,iEACD;QACC,CAAA;OACA,EAAA,CAAA;MAEN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,UAAD;QACE,MAAK;QACL,UAAU,KAAgB,CAAC;QAC3B,WAAU;kBAHZ,CAKG,IACC,kBAAC,GAAD,EAAS,WAAU,uBAAwB,CAAA,GAE3C,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAE7B,EAAG,4BAA4B,oBAAoB,CAC7C;WACT,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,EAAS,GAAG;QAC3B,WAAU;kBAET,EAAG,iBAAiB,SAAS;QACvB,CAAA,CACL;;MACD;;IACH;;EACU,CAAA"}
|
|
1
|
+
{"version":3,"file":"CreateConnectionPage.js","names":[],"sources":["../../../../../src/channel/modules/channels/pages/CreateConnectionPage.tsx"],"sourcesContent":["/**\n * Create Connection Page\n *\n * Standalone page mounted at /channels/connections/create.\n * User picks a target channel, then provides auth details.\n *\n * Form fields match CreateConnectionInput exactly (less channelId which is\n * resolved from the selector).\n */\n\nimport { useMemo, useState } from 'react';\nimport { useNavigate, useSearchParams } 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 { useChannel, useChannels, type ChannelAuthType } from '../../../hooks/useChannels';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\n\nconst AUTH_TYPES: { value: ChannelAuthType; label: string; help: string }[] = [\n { value: 'OAUTH2', label: 'OAuth 2.0', help: 'Authorization code flow' },\n { value: 'API_KEY', label: 'API Key', help: 'Static API key' },\n { value: 'BOT_TOKEN', label: 'Bot Token', help: 'Slack/Discord/Teams bot token' },\n { value: 'WEBHOOK', label: 'Webhook', help: 'Shared secret for HMAC verification' },\n { value: 'SMTP_IMAP', label: 'SMTP / IMAP', help: 'Mail server credentials' },\n];\n\nexport function CreateConnectionPage() {\n const navigate = useNavigate();\n const [searchParams] = useSearchParams();\n const { organizationId } = useChannelContext();\n const { channels, isLoading: channelsLoading } = 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 const initialChannelId = searchParams.get('channelId') ?? '';\n const [selectedChannelId, setSelectedChannelId] = useState<string>(initialChannelId);\n const { createConnection } = useChannel(selectedChannelId);\n\n const [authType, setAuthType] = useState<ChannelAuthType>('OAUTH2');\n const [authCode, setAuthCode] = useState('');\n const [apiKey, setApiKey] = useState('');\n const [redirectUri, setRedirectUri] = useState('');\n const [credentials, setCredentials] = useState('');\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const selectedChannel = useMemo(\n () => channels.find((c) => c.id === selectedChannelId) ?? null,\n [channels, selectedChannelId]\n );\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n\n if (!selectedChannelId) {\n setError(tr('connection.validation.channelRequired', 'Pick a channel to connect'));\n return;\n }\n if (!organizationId) {\n setError(tr('connection.validation.organizationRequired', 'Organization context missing'));\n return;\n }\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 setIsSubmitting(true);\n try {\n const result = await createConnection({\n authType,\n authCode: authCode.trim() || undefined,\n apiKey: apiKey.trim() || undefined,\n redirectUri: redirectUri.trim() || undefined,\n credentials: parsedCredentials,\n });\n if (result) {\n navigate(`../${result.id}`);\n } else {\n setError(tr('connection.createFailed', 'Failed to create connection'));\n }\n } catch (err: unknown) {\n setError(\n err instanceof Error\n ? err.message\n : tr('connection.createFailed', 'Failed to create connection')\n );\n } finally {\n setIsSubmitting(false);\n }\n };\n\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_CONNECTION_CREATE}>\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.create.title', 'Create Connection')}\n </h1>\n <p className=\"text-sm text-fg-muted\">\n {tr(\n 'connection.create.subtitle',\n 'Authenticate a channel against its platform provider.'\n )}\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>\n <label className=\"block text-sm font-medium text-fg mb-2\">\n {tr('connection.field.channel', 'Channel')} *\n </label>\n <select\n value={selectedChannelId}\n onChange={(e) => setSelectedChannelId(e.target.value)}\n className=\"w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg\"\n required\n disabled={channelsLoading}\n >\n <option value=\"\">\n {channelsLoading\n ? tr('common.loading', 'Loading...')\n : tr('connection.field.channelPlaceholder', 'Select a channel')}\n </option>\n {channels.map((c) => (\n <option key={c.id} value={c.id}>\n {c.name} ({c.platform})\n </option>\n ))}\n </select>\n {selectedChannel && (\n <p className=\"mt-1 text-xs text-fg-muted\">\n {tr('connection.field.channelHint', 'Adapter: ')}\n {selectedChannel.adapterType ?? '—'}\n </p>\n )}\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-fg mb-2\">\n {tr('connection.field.authType', 'Authentication Type')} *\n </label>\n <select\n value={authType}\n onChange={(e) => setAuthType(e.target.value as ChannelAuthType)}\n className=\"w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg\"\n required\n >\n {AUTH_TYPES.map((a) => (\n <option key={a.value} value={a.value}>\n {a.label}: {a.help}\n </option>\n ))}\n </select>\n </div>\n\n {authType === 'OAUTH2' && (\n <>\n <div>\n <label\n htmlFor=\"create-conn-auth-code\"\n className=\"block text-sm font-medium text-fg mb-2\"\n >\n {tr('connection.field.authCode', 'OAuth Authorization Code')}\n </label>\n <input\n id=\"create-conn-auth-code\"\n aria-label={tr('connection.field.authCode', 'OAuth Authorization Code')}\n type=\"text\"\n value={authCode}\n onChange={(e) => setAuthCode(e.target.value)}\n className=\"w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg\"\n autoComplete=\"off\"\n />\n </div>\n <div>\n <label\n htmlFor=\"create-conn-redirect-uri\"\n className=\"block text-sm font-medium text-fg mb-2\"\n >\n {tr('connection.field.redirectUri', 'Redirect URI')}\n </label>\n <input\n id=\"create-conn-redirect-uri\"\n aria-label={tr('connection.field.redirectUri', 'Redirect URI')}\n type=\"url\"\n value={redirectUri}\n onChange={(e) => setRedirectUri(e.target.value)}\n className=\"w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg\"\n />\n </div>\n </>\n )}\n\n {(authType === 'API_KEY' || authType === 'BOT_TOKEN') && (\n <div>\n <label\n htmlFor=\"create-conn-api-key\"\n className=\"block text-sm font-medium text-fg mb-2\"\n >\n {tr('connection.field.apiKey', 'API Key / Token')}\n </label>\n <input\n id=\"create-conn-api-key\"\n aria-label={tr('connection.field.apiKey', 'API Key / Token')}\n type=\"password\"\n value={apiKey}\n onChange={(e) => setApiKey(e.target.value)}\n className=\"w-full px-3 py-2 bg-bg border border-border-subtle rounded-md text-fg\"\n autoComplete=\"new-password\"\n />\n </div>\n )}\n\n <div>\n <label className=\"block text-sm font-medium text-fg mb-2\">\n {tr('connection.field.credentials', 'Additional Credentials (JSON)')}\n </label>\n <textarea\n aria-label='{\"smtpHost\": \"smtp.example.com\"}'\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='{\"smtpHost\": \"smtp.example.com\"}'\n />\n <p className=\"mt-1 text-xs text-fg-muted\">\n {tr(\n 'connection.field.credentialsHint',\n 'Encrypted at rest with AES-256-GCM. Never logged in plaintext.'\n )}\n </p>\n </div>\n\n <div className=\"flex gap-3 pt-2\">\n <button\n type=\"submit\"\n disabled={isSubmitting || !selectedChannelId}\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('connection.create.submit', 'Create Connection')}\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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"}
|