@burdenoff/microfe-bigconsole 2026.924.3 → 2026.925.2

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.
Files changed (27) hide show
  1. package/dist/bigconsole/BigConsoleRoot.js +94 -84
  2. package/dist/bigconsole/BigConsoleRoot.js.map +1 -1
  3. package/dist/bigconsole/assistant/previewController.js +6 -6
  4. package/dist/bigconsole/components/dashboard/ShareDialog/EmbedTab.js +142 -145
  5. package/dist/bigconsole/components/dashboard/ShareDialog/EmbedTab.js.map +1 -1
  6. package/dist/bigconsole/components/datasink/ImportDataSinkDialog.js +73 -61
  7. package/dist/bigconsole/components/datasink/ImportDataSinkDialog.js.map +1 -1
  8. package/dist/bigconsole/components/embed/ReadOnlyDashboardRenderer.js +5 -5
  9. package/dist/bigconsole/components/embed/ReadOnlyDashboardRenderer.js.map +1 -1
  10. package/dist/bigconsole/components/embed/types.js.map +1 -1
  11. package/dist/bigconsole/components/preview/AiPreviewMiniPanel.js +4 -4
  12. package/dist/bigconsole/components/preview/AiPreviewPanel.js +5 -5
  13. package/dist/bigconsole/hooks/useDashboardEmbedPolicy.js +2 -4
  14. package/dist/bigconsole/hooks/useDashboardEmbedPolicy.js.map +1 -1
  15. package/dist/bigconsole/hooks/useDataSinkOperations.js +92 -89
  16. package/dist/bigconsole/hooks/useDataSinkOperations.js.map +1 -1
  17. package/dist/bigconsole/hooks/useImportDataSink.js +96 -91
  18. package/dist/bigconsole/hooks/useImportDataSink.js.map +1 -1
  19. package/dist/bigconsole/pages/DataSinkBuilderPage.js +79 -77
  20. package/dist/bigconsole/pages/DataSinkBuilderPage.js.map +1 -1
  21. package/dist/bigconsole/providers/BigConsoleQuotaProvider.js +36 -35
  22. package/dist/bigconsole/providers/BigConsoleQuotaProvider.js.map +1 -1
  23. package/dist/bigconsole/utils/quota.js +51 -23
  24. package/dist/bigconsole/utils/quota.js.map +1 -1
  25. package/dist/generated/wspace-operations.js +0 -1
  26. package/dist/generated/wspace-operations.js.map +1 -1
  27. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"DataSinkBuilderPage.js","names":[],"sources":["../../../src/bigconsole/pages/DataSinkBuilderPage.tsx"],"sourcesContent":["/**\n * DataSink Builder Page\n *\n * Simplified page for creating and editing DataSinks.\n * DataSinks are now standalone - workflows push data to them via unique keys.\n * Required fields: name, key\n */\n\nimport { memo, useState, useMemo, useEffect, useCallback } from 'react';\nimport { useParams } from 'react-router';\nimport { useAppNavigate } from '../contexts';\nimport { isNative, nativeCopyText, nativeToast } from '../../utils/nativeBridge';\nimport { useDataSinkOperations } from '../hooks/useDataSinkOperations';\nimport { CacheTTLSelector } from '../components/datasink/CacheTTLSelector';\nimport { RefreshTypeSelector } from '../components/datasink/RefreshTypeSelector';\nimport type { CreateDataSinkInput, UpdateDataSinkInput, DataSinkRefreshType } from '../types';\n\n// fe-libs UI components\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Input } from '@burdenoff/fe-libs/ui';\nimport { Textarea } from '@burdenoff/fe-libs/ui';\nimport { Label } from '@burdenoff/fe-libs/ui';\nimport { IllustratedEmptyState, PagePurpose } from '@burdenoff/fe-libs/ui';\n\n// Section-emphasis primitives (BOFF-5632)\nimport { Section, SectionStack } from '../components/common';\n\n// Lucide icons\nimport { ArrowLeft, Save, Loader2, AlertCircle, Database, Settings, Eye, Copy, Check, ListChecks } from 'lucide-react';\n\n// RBAC\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { AccessDenied, PermissionButton } from '@burdenoff/fe-libs/shared/components';\nimport { BIGCONSOLE_PERMISSIONS } from '../../constants/permissions';\n\ntype TranslateFn = (key: string, fallback: string, params?: Record<string, string | number>) => string;\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\ninterface RefreshConfig {\n type: 'manual' | 'scheduled';\n schedule?: {\n cron: string;\n timezone: string;\n };\n}\n\ninterface FormState {\n name: string;\n key: string;\n description: string;\n ttl: number;\n refresh: RefreshConfig;\n data: string; // JSON string for data payload\n}\n\ninterface FormErrors {\n name?: string;\n key?: string;\n data?: string;\n}\n\n// ============================================================================\n// HELPERS\n// ============================================================================\n\n/**\n * Generate a key from name (slug format)\n */\nfunction generateKey(name: string): string {\n return name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '_')\n .replace(/^_+|_+$/g, '')\n .substring(0, 64);\n}\n\n/**\n * Validate form data\n */\nfunction validateForm(form: FormState, t: TranslateFn): FormErrors {\n const errors: FormErrors = {};\n\n if (!form.name.trim()) {\n errors.name = t('bigconsole.dataSinks.builder.validation.nameRequired', 'Name is required');\n } else if (form.name.length < 2) {\n errors.name = t('bigconsole.dataSinks.builder.validation.nameMinLength', 'Name must be at least 2 characters');\n } else if (form.name.length > 100) {\n errors.name = t('bigconsole.dataSinks.builder.validation.nameMaxLength', 'Name must be less than 100 characters');\n }\n\n if (!form.key.trim()) {\n errors.key = t('bigconsole.dataSinks.builder.validation.keyRequired', 'Key is required');\n } else if (!/^[a-z0-9_]+$/.test(form.key)) {\n errors.key = t(\n 'bigconsole.dataSinks.builder.validation.keyFormat',\n 'Key can only contain lowercase letters, numbers, and underscores'\n );\n } else if (form.key.length > 64) {\n errors.key = t('bigconsole.dataSinks.builder.validation.keyMaxLength', 'Key must be less than 64 characters');\n }\n\n // Validate JSON data if provided\n if (form.data && form.data.trim()) {\n try {\n JSON.parse(form.data);\n } catch {\n errors.data = t('bigconsole.dataSinks.builder.validation.invalidJson', 'Invalid JSON format');\n }\n }\n\n return errors;\n}\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\nexport const DataSinkBuilderPage = memo(function DataSinkBuilderPage() {\n const { dataSinkId } = useParams<{ dataSinkId?: string }>();\n const navigate = useAppNavigate();\n const { t } = useI18n();\n\n const isEdit = dataSinkId !== 'new' && !!dataSinkId;\n\n // RBAC — Save requires UPDATE when editing an existing sink, CREATE when\n // creating a new one. In edit mode the page also requires READ to be viewed.\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const canRead = hasPermission(BIGCONSOLE_PERMISSIONS.DATASINK_READ);\n const savePermission = isEdit ? BIGCONSOLE_PERMISSIONS.DATASINK_UPDATE : BIGCONSOLE_PERMISSIONS.DATASINK_CREATE;\n\n // Form state\n const [form, setForm] = useState<FormState>({\n name: '',\n key: '',\n description: '',\n ttl: 300, // 5 minutes default\n refresh: {\n type: 'manual',\n },\n data: '{}', // Default empty JSON object\n });\n const [errors, setErrors] = useState<FormErrors>({});\n const [keyManuallyEdited, setKeyManuallyEdited] = useState(false);\n const [isSaving, setIsSaving] = useState(false);\n const [submitError, setSubmitError] = useState<string | null>(null);\n const [keyCopied, setKeyCopied] = useState(false);\n\n // Data preview state\n const [isLoadingPreview, setIsLoadingPreview] = useState(false);\n const [previewData, setPreviewData] = useState<unknown[] | null>(null);\n const [previewError, setPreviewError] = useState<string | null>(null);\n\n // Operations\n const { loading, error, dataSinks, fetchDataSink, createDataSink, updateDataSink, getDataSinkData } =\n useDataSinkOperations({\n skipFetch: true,\n });\n\n // Get existing data sink if editing\n const existingDataSink = isEdit ? dataSinks.find((ds) => ds.id === dataSinkId) : null;\n\n // Load existing data sink for editing\n useEffect(() => {\n if (isEdit && dataSinkId && !existingDataSink) {\n fetchDataSink(dataSinkId);\n }\n }, [isEdit, dataSinkId, existingDataSink, fetchDataSink]);\n\n // Track if form has been initialized\n const [formInitialized, setFormInitialized] = useState(false);\n\n // Populate form with existing data when editing (only once)\n useEffect(() => {\n if (existingDataSink && !formInitialized) {\n // Format existing data as pretty JSON string\n let dataString = '';\n if (existingDataSink.data) {\n try {\n // Check if data is already a string (JSON string from API)\n if (typeof existingDataSink.data === 'string') {\n // Parse and re-stringify to ensure proper formatting\n const parsed = JSON.parse(existingDataSink.data);\n dataString = JSON.stringify(parsed, null, 2);\n } else {\n // It's an object, just stringify it\n dataString = JSON.stringify(existingDataSink.data, null, 2);\n }\n } catch {\n // If parsing fails, use as-is if string, otherwise empty\n dataString = typeof existingDataSink.data === 'string' ? existingDataSink.data : '';\n }\n }\n\n setForm({\n name: existingDataSink.name || '',\n key: existingDataSink.key || '',\n description: existingDataSink.description || '',\n ttl: existingDataSink.ttl || 300,\n refresh: existingDataSink.refresh\n ? {\n type: existingDataSink.refresh.type === 'scheduled' ? 'scheduled' : 'manual',\n schedule: existingDataSink.refresh.schedule\n ? {\n cron: existingDataSink.refresh.schedule.cron,\n timezone: existingDataSink.refresh.schedule.timezone,\n }\n : undefined,\n }\n : { type: 'manual' },\n data: dataString,\n });\n setKeyManuallyEdited(true); // Don't auto-generate key when editing\n setFormInitialized(true); // Mark as initialized\n setErrors({}); // Clear any errors\n }\n }, [existingDataSink, formInitialized]);\n\n // Auto-generate key from name\n const handleNameChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n const name = e.target.value;\n setForm((prev) => ({\n ...prev,\n name,\n key: keyManuallyEdited ? prev.key : generateKey(name),\n }));\n setErrors((prev) => ({ ...prev, name: undefined }));\n },\n [keyManuallyEdited]\n );\n\n // Handle key change\n const handleKeyChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {\n const key = e.target.value.toLowerCase().replace(/[^a-z0-9_]/g, '');\n setForm((prev) => ({ ...prev, key }));\n setKeyManuallyEdited(true);\n setErrors((prev) => ({ ...prev, key: undefined }));\n }, []);\n\n // Handle description change\n const handleDescriptionChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {\n setForm((prev) => ({ ...prev, description: e.target.value }));\n }, []);\n\n // Handle TTL change\n const handleTTLChange = useCallback((ttl: number | null) => {\n setForm((prev) => ({ ...prev, ttl: ttl || 0 }));\n }, []);\n\n // Handle refresh config change\n const handleRefreshChange = useCallback((config: RefreshConfig) => {\n setForm((prev) => ({ ...prev, refresh: config }));\n }, []);\n\n // Handle data change\n const handleDataChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {\n setForm((prev) => ({ ...prev, data: e.target.value }));\n setErrors((prev) => ({ ...prev, data: undefined }));\n }, []);\n\n // Format JSON data\n const handleFormatData = useCallback(() => {\n try {\n const parsed = JSON.parse(form.data);\n setForm((prev) => ({ ...prev, data: JSON.stringify(parsed, null, 2) }));\n setErrors((prev) => ({ ...prev, data: undefined }));\n } catch {\n setErrors((prev) => ({\n ...prev,\n data: t('bigconsole.dataSinks.builder.validation.invalidJson', 'Invalid JSON format'),\n }));\n }\n }, [form.data, t]);\n\n // Copy key to clipboard\n const handleCopyKey = useCallback(async () => {\n if (form.key) {\n await nativeCopyText(form.key);\n setKeyCopied(true);\n if (isNative()) void nativeToast(t('bigconsole.dataSinks.builder.toolbar.keyCopied', 'Key copied'));\n setTimeout(() => setKeyCopied(false), 2000);\n }\n }, [form.key, t]);\n\n // Handle back navigation\n const handleBack = useCallback(() => {\n navigate('/datasinks');\n }, [navigate]);\n\n // Load data preview\n const handleShowData = useCallback(async () => {\n if (!isEdit || !existingDataSink) return;\n\n setIsLoadingPreview(true);\n setPreviewError(null);\n\n try {\n const result = await getDataSinkData({ key: existingDataSink.key });\n if (result?.raw) {\n // Get first 20 items if it's an array, otherwise wrap in array\n const data = Array.isArray(result.raw) ? result.raw.slice(0, 20) : [result.raw];\n setPreviewData(data);\n } else {\n setPreviewData([]);\n }\n } catch (err) {\n setPreviewError(\n err instanceof Error\n ? err.message\n : t('bigconsole.dataSinks.builder.errors.loadDataFailed', 'Failed to load data')\n );\n } finally {\n setIsLoadingPreview(false);\n }\n }, [isEdit, existingDataSink, getDataSinkData, t]);\n\n // Handle form submission\n const handleSubmit = useCallback(\n async (e: React.FormEvent) => {\n e.preventDefault();\n setSubmitError(null);\n\n // Validate form\n const validationErrors = validateForm(form, t);\n if (Object.keys(validationErrors).length > 0) {\n setErrors(validationErrors);\n return;\n }\n\n setIsSaving(true);\n try {\n // Parse data JSON\n let parsedData: Record<string, unknown> = {};\n if (form.data && form.data.trim()) {\n try {\n parsedData = JSON.parse(form.data) as Record<string, unknown>;\n } catch {\n setErrors((prev) => ({\n ...prev,\n data: t('bigconsole.dataSinks.builder.validation.invalidJson', 'Invalid JSON format'),\n }));\n setIsSaving(false);\n return;\n }\n }\n\n if (isEdit && existingDataSink) {\n // Update existing\n const input: UpdateDataSinkInput = {\n id: existingDataSink.id,\n version: existingDataSink.version,\n name: form.name,\n description: form.description || undefined,\n ttl: form.ttl,\n refresh: {\n type: form.refresh.type as DataSinkRefreshType,\n schedule: form.refresh.schedule,\n },\n data: parsedData, // Include data in update\n };\n const result = await updateDataSink(input);\n if (!result) {\n throw new Error(t('bigconsole.dataSinks.builder.errors.updateFailed', 'Failed to update DataSink'));\n }\n } else {\n // Create new - simplified flow (standalone DataSinks)\n const input: CreateDataSinkInput = {\n key: form.key,\n name: form.name,\n description: form.description || undefined,\n ttl: form.ttl,\n refresh: {\n type: form.refresh.type as DataSinkRefreshType,\n schedule: form.refresh.schedule,\n },\n data: parsedData, // Include data in create\n };\n const result = await createDataSink(input);\n if (!result) {\n throw new Error(t('bigconsole.dataSinks.builder.errors.createFailed', 'Failed to create DataSink'));\n }\n }\n navigate('/datasinks');\n } catch (err) {\n setSubmitError(\n err instanceof Error\n ? err.message\n : t('bigconsole.dataSinks.builder.errors.saveFailed', 'Failed to save DataSink')\n );\n } finally {\n setIsSaving(false);\n }\n },\n [form, isEdit, existingDataSink, createDataSink, updateDataSink, navigate, t]\n );\n\n // Check if form is valid\n const isFormValid = useMemo(() => {\n const nameValid = form.name.trim().length >= 2;\n const keyValid = form.key.trim().length > 0;\n\n // Check JSON validity if data is provided\n let dataValid = true;\n if (form.data && form.data.trim()) {\n try {\n JSON.parse(form.data);\n } catch {\n dataValid = false;\n }\n }\n\n return nameValid && keyValid && dataValid;\n }, [form.name, form.key, form.data]);\n\n // Loading state\n if (loading && isEdit) {\n return (\n <div className=\"flex min-h-[240px] items-center justify-center p-card-padding\">\n <Loader2 className=\"h-icon-lg w-icon-lg animate-spin text-text-secondary\" />\n </div>\n );\n }\n\n // Error state\n if (error) {\n return (\n <div className=\"mx-auto w-full max-w-3xl p-card-padding\">\n <Section\n tone=\"danger\"\n headingLevel={2}\n title={t('bigconsole.dataSinks.builder.errors.loadFailed', 'Failed to load DataSink')}\n description={error.message}\n icon={<AlertCircle className=\"h-icon-sm w-icon-sm\" />}\n actions={\n <Button onClick={handleBack} variant=\"secondary\" size=\"sm\">\n <ArrowLeft className=\"w-4 h-4 mr-2\" />\n {t('bigconsole.dataSinks.builder.header.backToDataSinks', 'Back to DataSinks')}\n </Button>\n }\n />\n </div>\n );\n }\n\n // Read gate — the edit surface requires read access to the existing sink.\n if (isEdit && !permissionsLoading && !canRead) {\n return (\n <AccessDenied\n title={t('bigconsole.dataSinks.builder.accessDenied.title', 'Access Denied')}\n description={t(\n 'bigconsole.dataSinks.builder.accessDenied.description',\n \"You don't have permission to view this data sink.\"\n )}\n />\n );\n }\n\n return (\n <div className=\"mx-auto w-full min-w-0 max-w-3xl p-card-padding\">\n {/* Page header strip */}\n <header className=\"mb-form-gap\">\n <Button onClick={handleBack} variant=\"ghost\" size=\"sm\" className=\"mb-4\">\n <ArrowLeft className=\"w-4 h-4 mr-2\" />\n {t('bigconsole.dataSinks.builder.header.backToDataSinks', 'Back to DataSinks')}\n </Button>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n {isEdit\n ? t('bigconsole.dataSinks.builder.header.editTitle', 'Edit DataSink')\n : t('bigconsole.dataSinks.builder.header.createTitle', 'Create DataSink')}\n </h1>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {isEdit\n ? t('bigconsole.dataSinks.builder.header.editSubtitle', 'Update the DataSink configuration.')\n : t(\n 'bigconsole.dataSinks.builder.header.createSubtitle',\n 'Create a new DataSink to store and cache data. Use the unique key to connect workflows.'\n )}\n </p>\n </header>\n\n <PagePurpose className=\"mb-section-gap\">\n {t(\n 'bigconsole.dataSinks.builder.header.purpose',\n 'A data sink is the named, structured store your dashboards read from. Give it a unique key, configure how long data is cached and how it refreshes, then point a workflow at that key to push data in. Get the basics right here and everything downstream connects to it.'\n )}\n </PagePurpose>\n\n {/* Submit failure — a status section, not a hand-rolled tinted div. */}\n {submitError && (\n <Section\n tone=\"danger\"\n headingLevel={2}\n title={t('bigconsole.dataSinks.builder.errors.saveFailed', 'Failed to save DataSink')}\n description={submitError}\n icon={<AlertCircle className=\"h-icon-sm w-icon-sm\" />}\n className=\"mb-section-gap\"\n />\n )}\n\n {/* Form */}\n <form onSubmit={handleSubmit}>\n <SectionStack>\n {/* The point of this route: the sink's identity. */}\n <Section\n emphasis=\"primary\"\n headingLevel={2}\n title={t('bigconsole.dataSinks.builder.basicInfo.title', 'Basic Information')}\n description={t('bigconsole.dataSinks.builder.basicInfo.subtitle', 'Essential details for your DataSink')}\n icon={<Database className=\"h-icon-sm w-icon-sm\" />}\n bodyClassName=\"space-y-form-gap\"\n >\n <>\n {/* Name field */}\n <div className=\"space-y-2\">\n <Label htmlFor=\"name\">\n {t('bigconsole.dataSinks.builder.basicInfo.nameLabel', 'Name')}{' '}\n <span className=\"text-status-error-text\">*</span>\n </Label>\n <Input\n id=\"name\"\n type=\"text\"\n value={form.name}\n onChange={handleNameChange}\n placeholder={t('bigconsole.dataSinks.builder.basicInfo.namePlaceholder', 'e.g., Sales Metrics')}\n className={errors.name ? 'border-status-error-border' : ''}\n />\n {errors.name && <p className=\"text-xs text-status-error-text\">{errors.name}</p>}\n </div>\n\n {/* Key field */}\n <div className=\"space-y-2\">\n <Label htmlFor=\"key\">\n {t('bigconsole.dataSinks.builder.basicInfo.keyLabel', 'Unique Key')}{' '}\n <span className=\"text-status-error-text\">*</span>\n </Label>\n <div className=\"flex gap-2\">\n <Input\n id=\"key\"\n type=\"text\"\n value={form.key}\n onChange={handleKeyChange}\n placeholder={t('bigconsole.dataSinks.builder.basicInfo.keyPlaceholder', 'sales_metrics')}\n disabled={isEdit}\n className={`font-mono flex-1 ${errors.key ? 'border-status-error-border' : ''}`}\n />\n {form.key && (\n <Button type=\"button\" onClick={handleCopyKey} variant=\"outline\" size=\"sm\" className=\"px-3\">\n {keyCopied ? (\n <Check className=\"w-4 h-4 text-status-success-text\" />\n ) : (\n <Copy className=\"w-4 h-4\" />\n )}\n </Button>\n )}\n </div>\n {errors.key && <p className=\"text-xs text-status-error-text\">{errors.key}</p>}\n <p className=\"text-xs text-text-muted\">\n {isEdit\n ? t(\n 'bigconsole.dataSinks.builder.basicInfo.keyHelpEdit',\n 'Key cannot be changed. Use this key in your workflows to push data.'\n )\n : t(\n 'bigconsole.dataSinks.builder.basicInfo.keyHelpCreate',\n 'Auto-generated from name. This key will be used by workflows to push data.'\n )}\n </p>\n </div>\n\n {/* Description field */}\n <div className=\"space-y-2\">\n <Label htmlFor=\"description\">\n {t('bigconsole.dataSinks.builder.basicInfo.descriptionLabel', 'Description')}\n </Label>\n <Textarea\n id=\"description\"\n value={form.description}\n onChange={handleDescriptionChange}\n placeholder={t(\n 'bigconsole.dataSinks.builder.basicInfo.descriptionPlaceholder',\n 'Describe what data this sink stores...'\n )}\n rows={2}\n className=\"resize-none\"\n />\n </div>\n </>\n </Section>\n\n {/* Configuration */}\n <Section\n headingLevel={2}\n title={t('bigconsole.dataSinks.builder.config.title', 'Configuration')}\n description={t('bigconsole.dataSinks.builder.config.subtitle', 'Cache and refresh settings')}\n icon={<Settings className=\"h-icon-sm w-icon-sm\" />}\n bodyClassName=\"space-y-form-gap\"\n >\n <>\n {/* Cache TTL — the field label lives here, so the selector's own\n heading is suppressed to avoid a duplicated label. */}\n <div className=\"space-y-2\">\n <Label>{t('bigconsole.dataSinks.builder.config.cacheDurationLabel', 'Cache Duration')}</Label>\n <CacheTTLSelector value={form.ttl} onChange={handleTTLChange} hideLabel />\n <p className=\"text-xs text-text-muted\">\n {t(\n 'bigconsole.dataSinks.builder.config.cacheDurationHelp',\n 'How long cached data remains valid before requiring refresh.'\n )}\n </p>\n </div>\n\n {/* Refresh Type */}\n <RefreshTypeSelector value={form.refresh} onChange={handleRefreshChange} />\n </>\n </Section>\n\n {/* Data Payload */}\n <Section\n headingLevel={2}\n title={t('bigconsole.dataSinks.builder.dataPayload.title', 'Data Payload')}\n description={\n isEdit\n ? t(\n 'bigconsole.dataSinks.builder.dataPayload.subtitleEdit',\n 'Edit the JSON data stored in this DataSink'\n )\n : t('bigconsole.dataSinks.builder.dataPayload.subtitleCreate', 'Optional initial data (JSON format)')\n }\n icon={<Database className=\"h-icon-sm w-icon-sm\" />}\n actions={\n <Button type=\"button\" onClick={handleFormatData} variant=\"outline\" size=\"sm\">\n {t('bigconsole.dataSinks.builder.dataPayload.formatJson', 'Format JSON')}\n </Button>\n }\n bodyClassName=\"space-y-2\"\n >\n <>\n <Textarea\n aria-label={t('bigconsole.dataSinks.builder.dataPayload.title', 'Data Payload')}\n value={form.data}\n onChange={handleDataChange}\n placeholder={t('bigconsole.dataSinks.builder.dataPayload.dataPlaceholder', '{\"key\": \"value\"}')}\n rows={10}\n className={`resize-y font-mono text-xs ${errors.data ? 'border-status-error-border' : ''}`}\n />\n {errors.data && <p className=\"text-xs text-status-error-text\">{errors.data}</p>}\n <p className=\"text-xs text-text-muted\">\n {t(\n 'bigconsole.dataSinks.builder.dataPayload.help',\n 'Enter valid JSON data. This data will be stored and can be accessed by widgets via parsers.'\n )}\n </p>\n </>\n </Section>\n\n {/* Data Preview — read-only inspector, so it sits below the editable\n sections at `muted`, not level with them. */}\n {isEdit && existingDataSink && (\n <Section\n emphasis=\"muted\"\n headingLevel={2}\n title={t('bigconsole.dataSinks.builder.preview.title', 'Data Preview')}\n description={t('bigconsole.dataSinks.builder.preview.subtitle', 'Preview the first 20 records')}\n icon={<Eye className=\"h-icon-sm w-icon-sm\" />}\n actions={\n <Button type=\"button\" onClick={handleShowData} variant=\"outline\" size=\"sm\" disabled={isLoadingPreview}>\n {isLoadingPreview ? (\n <>\n <Loader2 className=\"w-4 h-4 mr-2 animate-spin\" />\n {t('bigconsole.dataSinks.builder.preview.loading', 'Loading...')}\n </>\n ) : (\n <>\n <Eye className=\"w-4 h-4 mr-2\" />\n {t('bigconsole.dataSinks.builder.preview.showData', 'Show Data')}\n </>\n )}\n </Button>\n }\n >\n {previewError ? (\n <Section tone=\"danger\" padding=\"compact\" headingLevel={3} title=\"Could not load the preview\">\n <p className=\"text-sm text-status-error-text\">{previewError}</p>\n </Section>\n ) : previewData && previewData.length > 0 ? (\n <pre className=\"max-h-[300px] min-w-0 overflow-auto rounded-input bg-bg-surface p-3 font-mono text-xs\">\n {JSON.stringify(previewData, null, 2)}\n </pre>\n ) : previewData ? (\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title={t('bigconsole.dataSinks.builder.preview.emptyTitle', 'No data available yet')}\n description={t(\n 'bigconsole.dataSinks.builder.preview.emptyDescription',\n \"Connect a workflow to this sink's key to start pushing data.\"\n )}\n />\n ) : (\n <p className=\"text-sm text-text-secondary\">\n Select &quot;Show data&quot; to load the records this sink currently holds.\n </p>\n )}\n </Section>\n )}\n\n {/* Guidance sits before the commit row — it is no longer advice that\n appears after the action which ends the task. */}\n {!isEdit && (\n <Section\n tone=\"info\"\n emphasis=\"muted\"\n headingLevel={2}\n title={t('bigconsole.dataSinks.builder.whatsNext.title', \"What's Next?\")}\n icon={<ListChecks className=\"h-icon-sm w-icon-sm\" />}\n >\n <ol className=\"list-inside list-decimal space-y-1 text-xs text-text-secondary\">\n <li>\n {t('bigconsole.dataSinks.builder.whatsNext.step1', 'Create this DataSink to get your unique key')}\n </li>\n <li>\n {t('bigconsole.dataSinks.builder.whatsNext.step2Prefix', 'Go to')}{' '}\n <strong>{t('bigconsole.dataSinks.builder.whatsNext.workflows', 'Workflows')}</strong>{' '}\n {t('bigconsole.dataSinks.builder.whatsNext.step2Suffix', 'and create a FluidGrids workflow')}\n </li>\n <li>\n {t(\n 'bigconsole.dataSinks.builder.whatsNext.step3',\n 'Use the unique key in your workflow to push data to this DataSink'\n )}\n </li>\n <li>\n {t('bigconsole.dataSinks.builder.whatsNext.step4Prefix', 'Create')}{' '}\n <strong>{t('bigconsole.dataSinks.builder.whatsNext.parsers', 'Parsers')}</strong>{' '}\n {t('bigconsole.dataSinks.builder.whatsNext.step4Suffix', 'to transform the data using JQ or JSONata')}\n </li>\n </ol>\n </Section>\n )}\n\n {/* Commit row — structural strip with a rule so the actions zone reads\n as the end of the form. */}\n <Section\n emphasis=\"bare\"\n padding=\"none\"\n aria-label=\"Save or discard this data sink\"\n className=\"border-t border-[var(--color-border-subtle)] pt-form-gap\"\n bodyClassName=\"flex flex-wrap items-center justify-end gap-control-gap\"\n >\n <Button type=\"button\" onClick={handleBack} variant=\"secondary\">\n {t('bigconsole.dataSinks.builder.actions.cancel', 'Cancel')}\n </Button>\n <PermissionButton permission={savePermission} deniedBehavior=\"disable\">\n <Button type=\"submit\" disabled={!isFormValid || isSaving} variant=\"default\">\n {isSaving ? (\n <>\n <Loader2 className=\"w-4 h-4 mr-2 animate-spin\" />\n {t('bigconsole.dataSinks.builder.actions.saving', 'Saving...')}\n </>\n ) : (\n <>\n <Save className=\"w-4 h-4 mr-2\" />\n {isEdit\n ? t('bigconsole.dataSinks.builder.actions.saveChanges', 'Save Changes')\n : t('bigconsole.dataSinks.builder.actions.create', 'Create DataSink')}\n </>\n )}\n </Button>\n </PermissionButton>\n </Section>\n </SectionStack>\n </form>\n </div>\n );\n});\n\nexport default DataSinkBuilderPage;\n"],"mappings":"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"}
1
+ {"version":3,"file":"DataSinkBuilderPage.js","names":[],"sources":["../../../src/bigconsole/pages/DataSinkBuilderPage.tsx"],"sourcesContent":["/**\n * DataSink Builder Page\n *\n * Simplified page for creating and editing DataSinks.\n * DataSinks are now standalone - workflows push data to them via unique keys.\n * Required fields: name, key\n */\n\nimport { memo, useState, useMemo, useEffect, useCallback } from 'react';\nimport { useParams } from 'react-router';\nimport { useAppNavigate } from '../contexts';\nimport { isNative, nativeCopyText, nativeToast } from '../../utils/nativeBridge';\nimport { useDataSinkOperations } from '../hooks/useDataSinkOperations';\nimport { CacheTTLSelector } from '../components/datasink/CacheTTLSelector';\nimport { RefreshTypeSelector } from '../components/datasink/RefreshTypeSelector';\nimport type { CreateDataSinkInput, UpdateDataSinkInput, DataSinkRefreshType } from '../types';\n\n// fe-libs UI components\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Input } from '@burdenoff/fe-libs/ui';\nimport { Textarea } from '@burdenoff/fe-libs/ui';\nimport { Label } from '@burdenoff/fe-libs/ui';\nimport { IllustratedEmptyState, PagePurpose } from '@burdenoff/fe-libs/ui';\n\n// Section-emphasis primitives (BOFF-5632)\nimport { Section, SectionStack } from '../components/common';\n\n// Lucide icons\nimport { ArrowLeft, Save, Loader2, AlertCircle, Database, Settings, Eye, Copy, Check, ListChecks } from 'lucide-react';\n\n// RBAC\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { AccessDenied, PermissionButton } from '@burdenoff/fe-libs/shared/components';\nimport { BIGCONSOLE_PERMISSIONS } from '../../constants/permissions';\nimport { reportBigConsoleQuotaError } from '../utils/quota';\n\ntype TranslateFn = (key: string, fallback: string, params?: Record<string, string | number>) => string;\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\ninterface RefreshConfig {\n type: 'manual' | 'scheduled';\n schedule?: {\n cron: string;\n timezone: string;\n };\n}\n\ninterface FormState {\n name: string;\n key: string;\n description: string;\n ttl: number;\n refresh: RefreshConfig;\n data: string; // JSON string for data payload\n}\n\ninterface FormErrors {\n name?: string;\n key?: string;\n data?: string;\n}\n\n// ============================================================================\n// HELPERS\n// ============================================================================\n\n/**\n * Generate a key from name (slug format)\n */\nfunction generateKey(name: string): string {\n return name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '_')\n .replace(/^_+|_+$/g, '')\n .substring(0, 64);\n}\n\n/**\n * Validate form data\n */\nfunction validateForm(form: FormState, t: TranslateFn): FormErrors {\n const errors: FormErrors = {};\n\n if (!form.name.trim()) {\n errors.name = t('bigconsole.dataSinks.builder.validation.nameRequired', 'Name is required');\n } else if (form.name.length < 2) {\n errors.name = t('bigconsole.dataSinks.builder.validation.nameMinLength', 'Name must be at least 2 characters');\n } else if (form.name.length > 100) {\n errors.name = t('bigconsole.dataSinks.builder.validation.nameMaxLength', 'Name must be less than 100 characters');\n }\n\n if (!form.key.trim()) {\n errors.key = t('bigconsole.dataSinks.builder.validation.keyRequired', 'Key is required');\n } else if (!/^[a-z0-9_]+$/.test(form.key)) {\n errors.key = t(\n 'bigconsole.dataSinks.builder.validation.keyFormat',\n 'Key can only contain lowercase letters, numbers, and underscores'\n );\n } else if (form.key.length > 64) {\n errors.key = t('bigconsole.dataSinks.builder.validation.keyMaxLength', 'Key must be less than 64 characters');\n }\n\n // Validate JSON data if provided\n if (form.data && form.data.trim()) {\n try {\n JSON.parse(form.data);\n } catch {\n errors.data = t('bigconsole.dataSinks.builder.validation.invalidJson', 'Invalid JSON format');\n }\n }\n\n return errors;\n}\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\nexport const DataSinkBuilderPage = memo(function DataSinkBuilderPage() {\n const { dataSinkId } = useParams<{ dataSinkId?: string }>();\n const navigate = useAppNavigate();\n const { t } = useI18n();\n\n const isEdit = dataSinkId !== 'new' && !!dataSinkId;\n\n // RBAC — Save requires UPDATE when editing an existing sink, CREATE when\n // creating a new one. In edit mode the page also requires READ to be viewed.\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const canRead = hasPermission(BIGCONSOLE_PERMISSIONS.DATASINK_READ);\n const savePermission = isEdit ? BIGCONSOLE_PERMISSIONS.DATASINK_UPDATE : BIGCONSOLE_PERMISSIONS.DATASINK_CREATE;\n\n // Form state\n const [form, setForm] = useState<FormState>({\n name: '',\n key: '',\n description: '',\n ttl: 300, // 5 minutes default\n refresh: {\n type: 'manual',\n },\n data: '{}', // Default empty JSON object\n });\n const [errors, setErrors] = useState<FormErrors>({});\n const [keyManuallyEdited, setKeyManuallyEdited] = useState(false);\n const [isSaving, setIsSaving] = useState(false);\n const [submitError, setSubmitError] = useState<string | null>(null);\n const [keyCopied, setKeyCopied] = useState(false);\n\n // Data preview state\n const [isLoadingPreview, setIsLoadingPreview] = useState(false);\n const [previewData, setPreviewData] = useState<unknown[] | null>(null);\n const [previewError, setPreviewError] = useState<string | null>(null);\n\n // Operations\n const { loading, error, dataSinks, fetchDataSink, createDataSink, updateDataSink, getDataSinkData } =\n useDataSinkOperations({\n skipFetch: true,\n });\n\n // Get existing data sink if editing\n const existingDataSink = isEdit ? dataSinks.find((ds) => ds.id === dataSinkId) : null;\n\n // Load existing data sink for editing\n useEffect(() => {\n if (isEdit && dataSinkId && !existingDataSink) {\n fetchDataSink(dataSinkId);\n }\n }, [isEdit, dataSinkId, existingDataSink, fetchDataSink]);\n\n // Track if form has been initialized\n const [formInitialized, setFormInitialized] = useState(false);\n\n // Populate form with existing data when editing (only once)\n useEffect(() => {\n if (existingDataSink && !formInitialized) {\n // Format existing data as pretty JSON string\n let dataString = '';\n if (existingDataSink.data) {\n try {\n // Check if data is already a string (JSON string from API)\n if (typeof existingDataSink.data === 'string') {\n // Parse and re-stringify to ensure proper formatting\n const parsed = JSON.parse(existingDataSink.data);\n dataString = JSON.stringify(parsed, null, 2);\n } else {\n // It's an object, just stringify it\n dataString = JSON.stringify(existingDataSink.data, null, 2);\n }\n } catch {\n // If parsing fails, use as-is if string, otherwise empty\n dataString = typeof existingDataSink.data === 'string' ? existingDataSink.data : '';\n }\n }\n\n setForm({\n name: existingDataSink.name || '',\n key: existingDataSink.key || '',\n description: existingDataSink.description || '',\n ttl: existingDataSink.ttl || 300,\n refresh: existingDataSink.refresh\n ? {\n type: existingDataSink.refresh.type === 'scheduled' ? 'scheduled' : 'manual',\n schedule: existingDataSink.refresh.schedule\n ? {\n cron: existingDataSink.refresh.schedule.cron,\n timezone: existingDataSink.refresh.schedule.timezone,\n }\n : undefined,\n }\n : { type: 'manual' },\n data: dataString,\n });\n setKeyManuallyEdited(true); // Don't auto-generate key when editing\n setFormInitialized(true); // Mark as initialized\n setErrors({}); // Clear any errors\n }\n }, [existingDataSink, formInitialized]);\n\n // Auto-generate key from name\n const handleNameChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n const name = e.target.value;\n setForm((prev) => ({\n ...prev,\n name,\n key: keyManuallyEdited ? prev.key : generateKey(name),\n }));\n setErrors((prev) => ({ ...prev, name: undefined }));\n },\n [keyManuallyEdited]\n );\n\n // Handle key change\n const handleKeyChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {\n const key = e.target.value.toLowerCase().replace(/[^a-z0-9_]/g, '');\n setForm((prev) => ({ ...prev, key }));\n setKeyManuallyEdited(true);\n setErrors((prev) => ({ ...prev, key: undefined }));\n }, []);\n\n // Handle description change\n const handleDescriptionChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {\n setForm((prev) => ({ ...prev, description: e.target.value }));\n }, []);\n\n // Handle TTL change\n const handleTTLChange = useCallback((ttl: number | null) => {\n setForm((prev) => ({ ...prev, ttl: ttl || 0 }));\n }, []);\n\n // Handle refresh config change\n const handleRefreshChange = useCallback((config: RefreshConfig) => {\n setForm((prev) => ({ ...prev, refresh: config }));\n }, []);\n\n // Handle data change\n const handleDataChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {\n setForm((prev) => ({ ...prev, data: e.target.value }));\n setErrors((prev) => ({ ...prev, data: undefined }));\n }, []);\n\n // Format JSON data\n const handleFormatData = useCallback(() => {\n try {\n const parsed = JSON.parse(form.data);\n setForm((prev) => ({ ...prev, data: JSON.stringify(parsed, null, 2) }));\n setErrors((prev) => ({ ...prev, data: undefined }));\n } catch {\n setErrors((prev) => ({\n ...prev,\n data: t('bigconsole.dataSinks.builder.validation.invalidJson', 'Invalid JSON format'),\n }));\n }\n }, [form.data, t]);\n\n // Copy key to clipboard\n const handleCopyKey = useCallback(async () => {\n if (form.key) {\n await nativeCopyText(form.key);\n setKeyCopied(true);\n if (isNative()) void nativeToast(t('bigconsole.dataSinks.builder.toolbar.keyCopied', 'Key copied'));\n setTimeout(() => setKeyCopied(false), 2000);\n }\n }, [form.key, t]);\n\n // Handle back navigation\n const handleBack = useCallback(() => {\n navigate('/datasinks');\n }, [navigate]);\n\n // Load data preview\n const handleShowData = useCallback(async () => {\n if (!isEdit || !existingDataSink) return;\n\n setIsLoadingPreview(true);\n setPreviewError(null);\n\n try {\n const result = await getDataSinkData({ key: existingDataSink.key });\n if (result?.raw) {\n // Get first 20 items if it's an array, otherwise wrap in array\n const data = Array.isArray(result.raw) ? result.raw.slice(0, 20) : [result.raw];\n setPreviewData(data);\n } else {\n setPreviewData([]);\n }\n } catch (err) {\n setPreviewError(\n err instanceof Error\n ? err.message\n : t('bigconsole.dataSinks.builder.errors.loadDataFailed', 'Failed to load data')\n );\n } finally {\n setIsLoadingPreview(false);\n }\n }, [isEdit, existingDataSink, getDataSinkData, t]);\n\n // Handle form submission\n const handleSubmit = useCallback(\n async (e: React.FormEvent) => {\n e.preventDefault();\n setSubmitError(null);\n\n // Validate form\n const validationErrors = validateForm(form, t);\n if (Object.keys(validationErrors).length > 0) {\n setErrors(validationErrors);\n return;\n }\n\n setIsSaving(true);\n try {\n // Parse data JSON\n let parsedData: Record<string, unknown> = {};\n if (form.data && form.data.trim()) {\n try {\n parsedData = JSON.parse(form.data) as Record<string, unknown>;\n } catch {\n setErrors((prev) => ({\n ...prev,\n data: t('bigconsole.dataSinks.builder.validation.invalidJson', 'Invalid JSON format'),\n }));\n setIsSaving(false);\n return;\n }\n }\n\n if (isEdit && existingDataSink) {\n // Update existing\n const input: UpdateDataSinkInput = {\n id: existingDataSink.id,\n version: existingDataSink.version,\n name: form.name,\n description: form.description || undefined,\n ttl: form.ttl,\n refresh: {\n type: form.refresh.type as DataSinkRefreshType,\n schedule: form.refresh.schedule,\n },\n data: parsedData, // Include data in update\n };\n const result = await updateDataSink(input);\n if (!result) {\n throw new Error(t('bigconsole.dataSinks.builder.errors.updateFailed', 'Failed to update DataSink'));\n }\n } else {\n // Create new - simplified flow (standalone DataSinks)\n const input: CreateDataSinkInput = {\n key: form.key,\n name: form.name,\n description: form.description || undefined,\n ttl: form.ttl,\n refresh: {\n type: form.refresh.type as DataSinkRefreshType,\n schedule: form.refresh.schedule,\n },\n data: parsedData, // Include data in create\n };\n const result = await createDataSink(input);\n if (!result) {\n throw new Error(t('bigconsole.dataSinks.builder.errors.createFailed', 'Failed to create DataSink'));\n }\n }\n navigate('/datasinks');\n } catch (err) {\n // A quota refusal opens the billing dialog (for a new org: \"Set up\n // billing\", BOFF-7913) instead of a dead-end error banner. The form\n // keeps its values so the user can retry once billing is set up.\n if (reportBigConsoleQuotaError(err)) {\n return;\n }\n setSubmitError(\n err instanceof Error\n ? err.message\n : t('bigconsole.dataSinks.builder.errors.saveFailed', 'Failed to save DataSink')\n );\n } finally {\n setIsSaving(false);\n }\n },\n [form, isEdit, existingDataSink, createDataSink, updateDataSink, navigate, t]\n );\n\n // Check if form is valid\n const isFormValid = useMemo(() => {\n const nameValid = form.name.trim().length >= 2;\n const keyValid = form.key.trim().length > 0;\n\n // Check JSON validity if data is provided\n let dataValid = true;\n if (form.data && form.data.trim()) {\n try {\n JSON.parse(form.data);\n } catch {\n dataValid = false;\n }\n }\n\n return nameValid && keyValid && dataValid;\n }, [form.name, form.key, form.data]);\n\n // Loading state\n if (loading && isEdit) {\n return (\n <div className=\"flex min-h-[240px] items-center justify-center p-card-padding\">\n <Loader2 className=\"h-icon-lg w-icon-lg animate-spin text-text-secondary\" />\n </div>\n );\n }\n\n // Error state\n if (error) {\n return (\n <div className=\"mx-auto w-full max-w-3xl p-card-padding\">\n <Section\n tone=\"danger\"\n headingLevel={2}\n title={t('bigconsole.dataSinks.builder.errors.loadFailed', 'Failed to load DataSink')}\n description={error.message}\n icon={<AlertCircle className=\"h-icon-sm w-icon-sm\" />}\n actions={\n <Button onClick={handleBack} variant=\"secondary\" size=\"sm\">\n <ArrowLeft className=\"w-4 h-4 mr-2\" />\n {t('bigconsole.dataSinks.builder.header.backToDataSinks', 'Back to DataSinks')}\n </Button>\n }\n />\n </div>\n );\n }\n\n // Read gate — the edit surface requires read access to the existing sink.\n if (isEdit && !permissionsLoading && !canRead) {\n return (\n <AccessDenied\n title={t('bigconsole.dataSinks.builder.accessDenied.title', 'Access Denied')}\n description={t(\n 'bigconsole.dataSinks.builder.accessDenied.description',\n \"You don't have permission to view this data sink.\"\n )}\n />\n );\n }\n\n return (\n <div className=\"mx-auto w-full min-w-0 max-w-3xl p-card-padding\">\n {/* Page header strip */}\n <header className=\"mb-form-gap\">\n <Button onClick={handleBack} variant=\"ghost\" size=\"sm\" className=\"mb-4\">\n <ArrowLeft className=\"w-4 h-4 mr-2\" />\n {t('bigconsole.dataSinks.builder.header.backToDataSinks', 'Back to DataSinks')}\n </Button>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n {isEdit\n ? t('bigconsole.dataSinks.builder.header.editTitle', 'Edit DataSink')\n : t('bigconsole.dataSinks.builder.header.createTitle', 'Create DataSink')}\n </h1>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {isEdit\n ? t('bigconsole.dataSinks.builder.header.editSubtitle', 'Update the DataSink configuration.')\n : t(\n 'bigconsole.dataSinks.builder.header.createSubtitle',\n 'Create a new DataSink to store and cache data. Use the unique key to connect workflows.'\n )}\n </p>\n </header>\n\n <PagePurpose className=\"mb-section-gap\">\n {t(\n 'bigconsole.dataSinks.builder.header.purpose',\n 'A data sink is the named, structured store your dashboards read from. Give it a unique key, configure how long data is cached and how it refreshes, then point a workflow at that key to push data in. Get the basics right here and everything downstream connects to it.'\n )}\n </PagePurpose>\n\n {/* Submit failure — a status section, not a hand-rolled tinted div. */}\n {submitError && (\n <Section\n tone=\"danger\"\n headingLevel={2}\n title={t('bigconsole.dataSinks.builder.errors.saveFailed', 'Failed to save DataSink')}\n description={submitError}\n icon={<AlertCircle className=\"h-icon-sm w-icon-sm\" />}\n className=\"mb-section-gap\"\n />\n )}\n\n {/* Form */}\n <form onSubmit={handleSubmit}>\n <SectionStack>\n {/* The point of this route: the sink's identity. */}\n <Section\n emphasis=\"primary\"\n headingLevel={2}\n title={t('bigconsole.dataSinks.builder.basicInfo.title', 'Basic Information')}\n description={t('bigconsole.dataSinks.builder.basicInfo.subtitle', 'Essential details for your DataSink')}\n icon={<Database className=\"h-icon-sm w-icon-sm\" />}\n bodyClassName=\"space-y-form-gap\"\n >\n <>\n {/* Name field */}\n <div className=\"space-y-2\">\n <Label htmlFor=\"name\">\n {t('bigconsole.dataSinks.builder.basicInfo.nameLabel', 'Name')}{' '}\n <span className=\"text-status-error-text\">*</span>\n </Label>\n <Input\n id=\"name\"\n type=\"text\"\n value={form.name}\n onChange={handleNameChange}\n placeholder={t('bigconsole.dataSinks.builder.basicInfo.namePlaceholder', 'e.g., Sales Metrics')}\n className={errors.name ? 'border-status-error-border' : ''}\n />\n {errors.name && <p className=\"text-xs text-status-error-text\">{errors.name}</p>}\n </div>\n\n {/* Key field */}\n <div className=\"space-y-2\">\n <Label htmlFor=\"key\">\n {t('bigconsole.dataSinks.builder.basicInfo.keyLabel', 'Unique Key')}{' '}\n <span className=\"text-status-error-text\">*</span>\n </Label>\n <div className=\"flex gap-2\">\n <Input\n id=\"key\"\n type=\"text\"\n value={form.key}\n onChange={handleKeyChange}\n placeholder={t('bigconsole.dataSinks.builder.basicInfo.keyPlaceholder', 'sales_metrics')}\n disabled={isEdit}\n className={`font-mono flex-1 ${errors.key ? 'border-status-error-border' : ''}`}\n />\n {form.key && (\n <Button type=\"button\" onClick={handleCopyKey} variant=\"outline\" size=\"sm\" className=\"px-3\">\n {keyCopied ? (\n <Check className=\"w-4 h-4 text-status-success-text\" />\n ) : (\n <Copy className=\"w-4 h-4\" />\n )}\n </Button>\n )}\n </div>\n {errors.key && <p className=\"text-xs text-status-error-text\">{errors.key}</p>}\n <p className=\"text-xs text-text-muted\">\n {isEdit\n ? t(\n 'bigconsole.dataSinks.builder.basicInfo.keyHelpEdit',\n 'Key cannot be changed. Use this key in your workflows to push data.'\n )\n : t(\n 'bigconsole.dataSinks.builder.basicInfo.keyHelpCreate',\n 'Auto-generated from name. This key will be used by workflows to push data.'\n )}\n </p>\n </div>\n\n {/* Description field */}\n <div className=\"space-y-2\">\n <Label htmlFor=\"description\">\n {t('bigconsole.dataSinks.builder.basicInfo.descriptionLabel', 'Description')}\n </Label>\n <Textarea\n id=\"description\"\n value={form.description}\n onChange={handleDescriptionChange}\n placeholder={t(\n 'bigconsole.dataSinks.builder.basicInfo.descriptionPlaceholder',\n 'Describe what data this sink stores...'\n )}\n rows={2}\n className=\"resize-none\"\n />\n </div>\n </>\n </Section>\n\n {/* Configuration */}\n <Section\n headingLevel={2}\n title={t('bigconsole.dataSinks.builder.config.title', 'Configuration')}\n description={t('bigconsole.dataSinks.builder.config.subtitle', 'Cache and refresh settings')}\n icon={<Settings className=\"h-icon-sm w-icon-sm\" />}\n bodyClassName=\"space-y-form-gap\"\n >\n <>\n {/* Cache TTL — the field label lives here, so the selector's own\n heading is suppressed to avoid a duplicated label. */}\n <div className=\"space-y-2\">\n <Label>{t('bigconsole.dataSinks.builder.config.cacheDurationLabel', 'Cache Duration')}</Label>\n <CacheTTLSelector value={form.ttl} onChange={handleTTLChange} hideLabel />\n <p className=\"text-xs text-text-muted\">\n {t(\n 'bigconsole.dataSinks.builder.config.cacheDurationHelp',\n 'How long cached data remains valid before requiring refresh.'\n )}\n </p>\n </div>\n\n {/* Refresh Type */}\n <RefreshTypeSelector value={form.refresh} onChange={handleRefreshChange} />\n </>\n </Section>\n\n {/* Data Payload */}\n <Section\n headingLevel={2}\n title={t('bigconsole.dataSinks.builder.dataPayload.title', 'Data Payload')}\n description={\n isEdit\n ? t(\n 'bigconsole.dataSinks.builder.dataPayload.subtitleEdit',\n 'Edit the JSON data stored in this DataSink'\n )\n : t('bigconsole.dataSinks.builder.dataPayload.subtitleCreate', 'Optional initial data (JSON format)')\n }\n icon={<Database className=\"h-icon-sm w-icon-sm\" />}\n actions={\n <Button type=\"button\" onClick={handleFormatData} variant=\"outline\" size=\"sm\">\n {t('bigconsole.dataSinks.builder.dataPayload.formatJson', 'Format JSON')}\n </Button>\n }\n bodyClassName=\"space-y-2\"\n >\n <>\n <Textarea\n aria-label={t('bigconsole.dataSinks.builder.dataPayload.title', 'Data Payload')}\n value={form.data}\n onChange={handleDataChange}\n placeholder={t('bigconsole.dataSinks.builder.dataPayload.dataPlaceholder', '{\"key\": \"value\"}')}\n rows={10}\n className={`resize-y font-mono text-xs ${errors.data ? 'border-status-error-border' : ''}`}\n />\n {errors.data && <p className=\"text-xs text-status-error-text\">{errors.data}</p>}\n <p className=\"text-xs text-text-muted\">\n {t(\n 'bigconsole.dataSinks.builder.dataPayload.help',\n 'Enter valid JSON data. This data will be stored and can be accessed by widgets via parsers.'\n )}\n </p>\n </>\n </Section>\n\n {/* Data Preview — read-only inspector, so it sits below the editable\n sections at `muted`, not level with them. */}\n {isEdit && existingDataSink && (\n <Section\n emphasis=\"muted\"\n headingLevel={2}\n title={t('bigconsole.dataSinks.builder.preview.title', 'Data Preview')}\n description={t('bigconsole.dataSinks.builder.preview.subtitle', 'Preview the first 20 records')}\n icon={<Eye className=\"h-icon-sm w-icon-sm\" />}\n actions={\n <Button type=\"button\" onClick={handleShowData} variant=\"outline\" size=\"sm\" disabled={isLoadingPreview}>\n {isLoadingPreview ? (\n <>\n <Loader2 className=\"w-4 h-4 mr-2 animate-spin\" />\n {t('bigconsole.dataSinks.builder.preview.loading', 'Loading...')}\n </>\n ) : (\n <>\n <Eye className=\"w-4 h-4 mr-2\" />\n {t('bigconsole.dataSinks.builder.preview.showData', 'Show Data')}\n </>\n )}\n </Button>\n }\n >\n {previewError ? (\n <Section tone=\"danger\" padding=\"compact\" headingLevel={3} title=\"Could not load the preview\">\n <p className=\"text-sm text-status-error-text\">{previewError}</p>\n </Section>\n ) : previewData && previewData.length > 0 ? (\n <pre className=\"max-h-[300px] min-w-0 overflow-auto rounded-input bg-bg-surface p-3 font-mono text-xs\">\n {JSON.stringify(previewData, null, 2)}\n </pre>\n ) : previewData ? (\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title={t('bigconsole.dataSinks.builder.preview.emptyTitle', 'No data available yet')}\n description={t(\n 'bigconsole.dataSinks.builder.preview.emptyDescription',\n \"Connect a workflow to this sink's key to start pushing data.\"\n )}\n />\n ) : (\n <p className=\"text-sm text-text-secondary\">\n Select &quot;Show data&quot; to load the records this sink currently holds.\n </p>\n )}\n </Section>\n )}\n\n {/* Guidance sits before the commit row — it is no longer advice that\n appears after the action which ends the task. */}\n {!isEdit && (\n <Section\n tone=\"info\"\n emphasis=\"muted\"\n headingLevel={2}\n title={t('bigconsole.dataSinks.builder.whatsNext.title', \"What's Next?\")}\n icon={<ListChecks className=\"h-icon-sm w-icon-sm\" />}\n >\n <ol className=\"list-inside list-decimal space-y-1 text-xs text-text-secondary\">\n <li>\n {t('bigconsole.dataSinks.builder.whatsNext.step1', 'Create this DataSink to get your unique key')}\n </li>\n <li>\n {t('bigconsole.dataSinks.builder.whatsNext.step2Prefix', 'Go to')}{' '}\n <strong>{t('bigconsole.dataSinks.builder.whatsNext.workflows', 'Workflows')}</strong>{' '}\n {t('bigconsole.dataSinks.builder.whatsNext.step2Suffix', 'and create a FluidGrids workflow')}\n </li>\n <li>\n {t(\n 'bigconsole.dataSinks.builder.whatsNext.step3',\n 'Use the unique key in your workflow to push data to this DataSink'\n )}\n </li>\n <li>\n {t('bigconsole.dataSinks.builder.whatsNext.step4Prefix', 'Create')}{' '}\n <strong>{t('bigconsole.dataSinks.builder.whatsNext.parsers', 'Parsers')}</strong>{' '}\n {t('bigconsole.dataSinks.builder.whatsNext.step4Suffix', 'to transform the data using JQ or JSONata')}\n </li>\n </ol>\n </Section>\n )}\n\n {/* Commit row — structural strip with a rule so the actions zone reads\n as the end of the form. */}\n <Section\n emphasis=\"bare\"\n padding=\"none\"\n aria-label=\"Save or discard this data sink\"\n className=\"border-t border-[var(--color-border-subtle)] pt-form-gap\"\n bodyClassName=\"flex flex-wrap items-center justify-end gap-control-gap\"\n >\n <Button type=\"button\" onClick={handleBack} variant=\"secondary\">\n {t('bigconsole.dataSinks.builder.actions.cancel', 'Cancel')}\n </Button>\n <PermissionButton permission={savePermission} deniedBehavior=\"disable\">\n <Button type=\"submit\" disabled={!isFormValid || isSaving} variant=\"default\">\n {isSaving ? (\n <>\n <Loader2 className=\"w-4 h-4 mr-2 animate-spin\" />\n {t('bigconsole.dataSinks.builder.actions.saving', 'Saving...')}\n </>\n ) : (\n <>\n <Save className=\"w-4 h-4 mr-2\" />\n {isEdit\n ? t('bigconsole.dataSinks.builder.actions.saveChanges', 'Save Changes')\n : t('bigconsole.dataSinks.builder.actions.create', 'Create DataSink')}\n </>\n )}\n </Button>\n </PermissionButton>\n </Section>\n </SectionStack>\n </form>\n </div>\n );\n});\n\nexport default DataSinkBuilderPage;\n"],"mappings":"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"}
@@ -1,70 +1,71 @@
1
- import { BIGCONSOLE_QUOTA_EXHAUSTED_EVENT as e, getBigConsoleQuotaDialogContent as t } from "../utils/quota.js";
2
- import { useCallback as n, useEffect as r, useState as i } from "react";
3
- import { AlertTriangle as a } from "lucide-react";
4
- import { Button as o, ResponsiveDialog as s, ResponsiveDialogContent as c, ResponsiveDialogDescription as l, ResponsiveDialogFooter as u, ResponsiveDialogHeader as d, ResponsiveDialogTitle as f } from "@burdenoff/fe-libs/ui";
5
- import { Fragment as p, jsx as m, jsxs as h } from "react/jsx-runtime";
1
+ import { BIGCONSOLE_BILLING_PATHS as e, BIGCONSOLE_QUOTA_EXHAUSTED_EVENT as t, getBigConsoleQuotaDialogContent as n } from "../utils/quota.js";
2
+ import { useCallback as r, useEffect as i, useState as a } from "react";
3
+ import { AlertTriangle as o } from "lucide-react";
4
+ import { Button as s, ResponsiveDialog as c, ResponsiveDialogContent as l, ResponsiveDialogDescription as u, ResponsiveDialogFooter as d, ResponsiveDialogHeader as f, ResponsiveDialogTitle as p } from "@burdenoff/fe-libs/ui";
5
+ import { Fragment as m, jsx as h, jsxs as g } from "react/jsx-runtime";
6
+ import { useI18n as _ } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
6
7
  //#region src/bigconsole/providers/BigConsoleQuotaProvider.tsx
7
- var g = {
8
+ var v = {
8
9
  open: !1,
9
10
  kind: "generic"
10
11
  };
11
- function _({ children: _, onViewBilling: v }) {
12
- let [y, b] = i(g);
13
- r(() => {
14
- let t = (e) => {
12
+ function y({ children: y, onViewBilling: b }) {
13
+ let { t: x } = _(), [S, C] = a(v);
14
+ i(() => {
15
+ let e = (e) => {
15
16
  let t = e.detail;
16
- b({
17
+ C({
17
18
  open: !0,
18
19
  kind: t?.kind ?? "generic"
19
20
  });
20
21
  };
21
- return window.addEventListener(e, t), () => {
22
- window.removeEventListener(e, t);
22
+ return window.addEventListener(t, e), () => {
23
+ window.removeEventListener(t, e);
23
24
  };
24
25
  }, []);
25
- let x = n(() => {
26
- b((e) => ({
26
+ let w = r(() => {
27
+ C((e) => ({
27
28
  ...e,
28
29
  open: !1
29
30
  }));
30
- }, []), S = n(() => {
31
- b((e) => ({
31
+ }, []), T = S.kind === "billing-setup", E = r(() => {
32
+ C((e) => ({
32
33
  ...e,
33
34
  open: !1
34
- })), v();
35
- }, [v]), C = t(y.kind);
36
- return /* @__PURE__ */ h(p, { children: [_, /* @__PURE__ */ m(s, {
37
- open: y.open,
38
- onOpenChange: (e) => !e && x(),
39
- children: /* @__PURE__ */ h(c, {
35
+ })), b(T ? e.billingSetup : e.billing);
36
+ }, [b, T]), D = n(S.kind), O = x(`bigconsole.quota.${S.kind}.title`, D.title), k = x(`bigconsole.quota.${S.kind}.message`, D.message);
37
+ return /* @__PURE__ */ g(m, { children: [y, /* @__PURE__ */ h(c, {
38
+ open: S.open,
39
+ onOpenChange: (e) => !e && w(),
40
+ children: /* @__PURE__ */ g(l, {
40
41
  className: "sm:max-w-md",
41
42
  showCloseButton: !1,
42
- children: [/* @__PURE__ */ h("div", {
43
+ children: [/* @__PURE__ */ g("div", {
43
44
  className: "flex items-start gap-3",
44
- children: [/* @__PURE__ */ m("div", {
45
+ children: [/* @__PURE__ */ h("div", {
45
46
  className: "flex size-10 shrink-0 items-center justify-center rounded-full bg-status-warning-bg-subtle text-status-warning-text",
46
- children: /* @__PURE__ */ m(a, {
47
+ children: /* @__PURE__ */ h(o, {
47
48
  className: "size-5",
48
49
  "aria-hidden": "true"
49
50
  })
50
- }), /* @__PURE__ */ h(d, {
51
+ }), /* @__PURE__ */ g(f, {
51
52
  className: "text-left",
52
- children: [/* @__PURE__ */ m(f, { children: C.title }), /* @__PURE__ */ m(l, { children: C.message })]
53
+ children: [/* @__PURE__ */ h(p, { children: O }), /* @__PURE__ */ h(u, { children: k })]
53
54
  })]
54
- }), /* @__PURE__ */ h(u, { children: [/* @__PURE__ */ m(o, {
55
+ }), /* @__PURE__ */ g(d, { children: [/* @__PURE__ */ h(s, {
55
56
  type: "button",
56
57
  variant: "outline",
57
- onClick: x,
58
- children: "Dismiss"
59
- }), /* @__PURE__ */ m(o, {
58
+ onClick: w,
59
+ children: x("bigconsole.quota.dismiss", "Dismiss")
60
+ }), /* @__PURE__ */ h(s, {
60
61
  type: "button",
61
- onClick: S,
62
- children: "View Billing"
62
+ onClick: E,
63
+ children: T ? x("bigconsole.quota.setUpBilling", "Set up billing") : x("bigconsole.quota.viewBilling", "View Billing")
63
64
  })] })]
64
65
  })
65
66
  })] });
66
67
  }
67
68
  //#endregion
68
- export { _ as BigConsoleQuotaProvider };
69
+ export { y as BigConsoleQuotaProvider };
69
70
 
70
71
  //# sourceMappingURL=BigConsoleQuotaProvider.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"BigConsoleQuotaProvider.js","names":[],"sources":["../../../src/bigconsole/providers/BigConsoleQuotaProvider.tsx"],"sourcesContent":["import { useCallback, useEffect, useState, type ReactNode } from 'react';\nimport { AlertTriangle } from 'lucide-react';\nimport {\n Button,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n} from '@burdenoff/fe-libs/ui';\nimport {\n BIGCONSOLE_QUOTA_EXHAUSTED_EVENT,\n getBigConsoleQuotaDialogContent,\n type BigConsoleQuotaExhaustedEventDetail,\n type BigConsoleQuotaKind,\n} from '../utils/quota';\n\ninterface BigConsoleQuotaProviderProps {\n children: ReactNode;\n onViewBilling: () => void;\n}\n\ninterface QuotaDialogState {\n open: boolean;\n kind: BigConsoleQuotaKind;\n}\n\nconst INITIAL_STATE: QuotaDialogState = { open: false, kind: 'generic' };\n\nexport function BigConsoleQuotaProvider({ children, onViewBilling }: BigConsoleQuotaProviderProps) {\n const [state, setState] = useState<QuotaDialogState>(INITIAL_STATE);\n\n useEffect(() => {\n const handleQuotaExhausted = (event: Event) => {\n const detail = (event as CustomEvent<BigConsoleQuotaExhaustedEventDetail>).detail;\n setState({ open: true, kind: detail?.kind ?? 'generic' });\n };\n\n window.addEventListener(BIGCONSOLE_QUOTA_EXHAUSTED_EVENT, handleQuotaExhausted);\n return () => {\n window.removeEventListener(BIGCONSOLE_QUOTA_EXHAUSTED_EVENT, handleQuotaExhausted);\n };\n }, []);\n\n const handleClose = useCallback(() => {\n setState((current) => ({ ...current, open: false }));\n }, []);\n\n const handleViewBilling = useCallback(() => {\n setState((current) => ({ ...current, open: false }));\n onViewBilling();\n }, [onViewBilling]);\n\n const content = getBigConsoleQuotaDialogContent(state.kind);\n\n return (\n <>\n {children}\n <ResponsiveDialog open={state.open} onOpenChange={(open) => !open && handleClose()}>\n <ResponsiveDialogContent className=\"sm:max-w-md\" showCloseButton={false}>\n <div className=\"flex items-start gap-3\">\n <div className=\"flex size-10 shrink-0 items-center justify-center rounded-full bg-status-warning-bg-subtle text-status-warning-text\">\n <AlertTriangle className=\"size-5\" aria-hidden=\"true\" />\n </div>\n <ResponsiveDialogHeader className=\"text-left\">\n <ResponsiveDialogTitle>{content.title}</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>{content.message}</ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n </div>\n <ResponsiveDialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={handleClose}>\n Dismiss\n </Button>\n <Button type=\"button\" onClick={handleViewBilling}>\n View Billing\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </>\n );\n}\n"],"mappings":";;;;;;AA4BA,IAAM,IAAkC;CAAE,MAAM;CAAO,MAAM;CAAW;AAExE,SAAgB,EAAwB,EAAE,aAAU,oBAA+C;CACjG,IAAM,CAAC,GAAO,KAAY,EAA2B,EAAc;AAEnE,SAAgB;EACd,IAAM,KAAwB,MAAiB;GAC7C,IAAM,IAAU,EAA2D;AAC3E,KAAS;IAAE,MAAM;IAAM,MAAM,GAAQ,QAAQ;IAAW,CAAC;;AAI3D,SADA,OAAO,iBAAiB,GAAkC,EAAqB,QAClE;AACX,UAAO,oBAAoB,GAAkC,EAAqB;;IAEnF,EAAE,CAAC;CAEN,IAAM,IAAc,QAAkB;AACpC,KAAU,OAAa;GAAE,GAAG;GAAS,MAAM;GAAO,EAAE;IACnD,EAAE,CAAC,EAEA,IAAoB,QAAkB;AAE1C,EADA,GAAU,OAAa;GAAE,GAAG;GAAS,MAAM;GAAO,EAAE,EACpD,GAAe;IACd,CAAC,EAAc,CAAC,EAEb,IAAU,EAAgC,EAAM,KAAK;AAE3D,QACE,kBAAA,GAAA,EAAA,UAAA,CACG,GACD,kBAAC,GAAD;EAAkB,MAAM,EAAM;EAAM,eAAe,MAAS,CAAC,KAAQ,GAAa;YAChF,kBAAC,GAAD;GAAyB,WAAU;GAAc,iBAAiB;aAAlE,CACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD;MAAe,WAAU;MAAS,eAAY;MAAS,CAAA;KACnD,CAAA,EACN,kBAAC,GAAD;KAAwB,WAAU;eAAlC,CACE,kBAAC,GAAD,EAAA,UAAwB,EAAQ,OAA8B,CAAA,EAC9D,kBAAC,GAAD,EAAA,UAA8B,EAAQ,SAAsC,CAAA,CACrD;OACrB;OACN,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;IAAQ,MAAK;IAAS,SAAQ;IAAU,SAAS;cAAa;IAErD,CAAA,EACT,kBAAC,GAAD;IAAQ,MAAK;IAAS,SAAS;cAAmB;IAEzC,CAAA,CACc,EAAA,CAAA,CACD;;EACT,CAAA,CAClB,EAAA,CAAA"}
1
+ {"version":3,"file":"BigConsoleQuotaProvider.js","names":[],"sources":["../../../src/bigconsole/providers/BigConsoleQuotaProvider.tsx"],"sourcesContent":["import { useCallback, useEffect, useState, type ReactNode } from 'react';\nimport { AlertTriangle } from 'lucide-react';\nimport {\n Button,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n} from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n BIGCONSOLE_BILLING_PATHS,\n BIGCONSOLE_QUOTA_EXHAUSTED_EVENT,\n getBigConsoleQuotaDialogContent,\n type BigConsoleQuotaExhaustedEventDetail,\n type BigConsoleQuotaKind,\n} from '../utils/quota';\n\ninterface BigConsoleQuotaProviderProps {\n children: ReactNode;\n /** Navigate to a billing route (e.g. `/billing` or `/billing/overview`). */\n onViewBilling: (path: string) => void;\n}\n\ninterface QuotaDialogState {\n open: boolean;\n kind: BigConsoleQuotaKind;\n}\n\nconst INITIAL_STATE: QuotaDialogState = { open: false, kind: 'generic' };\n\nexport function BigConsoleQuotaProvider({ children, onViewBilling }: BigConsoleQuotaProviderProps) {\n const { t } = useI18n();\n const [state, setState] = useState<QuotaDialogState>(INITIAL_STATE);\n\n useEffect(() => {\n const handleQuotaExhausted = (event: Event) => {\n const detail = (event as CustomEvent<BigConsoleQuotaExhaustedEventDetail>).detail;\n setState({ open: true, kind: detail?.kind ?? 'generic' });\n };\n\n window.addEventListener(BIGCONSOLE_QUOTA_EXHAUSTED_EVENT, handleQuotaExhausted);\n return () => {\n window.removeEventListener(BIGCONSOLE_QUOTA_EXHAUSTED_EVENT, handleQuotaExhausted);\n };\n }, []);\n\n const handleClose = useCallback(() => {\n setState((current) => ({ ...current, open: false }));\n }, []);\n\n const isBillingSetup = state.kind === 'billing-setup';\n\n const handleViewBilling = useCallback(() => {\n setState((current) => ({ ...current, open: false }));\n onViewBilling(isBillingSetup ? BIGCONSOLE_BILLING_PATHS.billingSetup : BIGCONSOLE_BILLING_PATHS.billing);\n }, [onViewBilling, isBillingSetup]);\n\n const content = getBigConsoleQuotaDialogContent(state.kind);\n const title = t(`bigconsole.quota.${state.kind}.title`, content.title);\n const message = t(`bigconsole.quota.${state.kind}.message`, content.message);\n\n return (\n <>\n {children}\n <ResponsiveDialog open={state.open} onOpenChange={(open) => !open && handleClose()}>\n <ResponsiveDialogContent className=\"sm:max-w-md\" showCloseButton={false}>\n <div className=\"flex items-start gap-3\">\n <div className=\"flex size-10 shrink-0 items-center justify-center rounded-full bg-status-warning-bg-subtle text-status-warning-text\">\n <AlertTriangle className=\"size-5\" aria-hidden=\"true\" />\n </div>\n <ResponsiveDialogHeader className=\"text-left\">\n <ResponsiveDialogTitle>{title}</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>{message}</ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n </div>\n <ResponsiveDialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={handleClose}>\n {t('bigconsole.quota.dismiss', 'Dismiss')}\n </Button>\n <Button type=\"button\" onClick={handleViewBilling}>\n {isBillingSetup\n ? t('bigconsole.quota.setUpBilling', 'Set up billing')\n : t('bigconsole.quota.viewBilling', 'View Billing')}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </>\n );\n}\n"],"mappings":";;;;;;;AA+BA,IAAM,IAAkC;CAAE,MAAM;CAAO,MAAM;CAAW;AAExE,SAAgB,EAAwB,EAAE,aAAU,oBAA+C;CACjG,IAAM,EAAE,SAAM,GAAS,EACjB,CAAC,GAAO,KAAY,EAA2B,EAAc;AAEnE,SAAgB;EACd,IAAM,KAAwB,MAAiB;GAC7C,IAAM,IAAU,EAA2D;AAC3E,KAAS;IAAE,MAAM;IAAM,MAAM,GAAQ,QAAQ;IAAW,CAAC;;AAI3D,SADA,OAAO,iBAAiB,GAAkC,EAAqB,QAClE;AACX,UAAO,oBAAoB,GAAkC,EAAqB;;IAEnF,EAAE,CAAC;CAEN,IAAM,IAAc,QAAkB;AACpC,KAAU,OAAa;GAAE,GAAG;GAAS,MAAM;GAAO,EAAE;IACnD,EAAE,CAAC,EAEA,IAAiB,EAAM,SAAS,iBAEhC,IAAoB,QAAkB;AAE1C,EADA,GAAU,OAAa;GAAE,GAAG;GAAS,MAAM;GAAO,EAAE,EACpD,EAAc,IAAiB,EAAyB,eAAe,EAAyB,QAAQ;IACvG,CAAC,GAAe,EAAe,CAAC,EAE7B,IAAU,EAAgC,EAAM,KAAK,EACrD,IAAQ,EAAE,oBAAoB,EAAM,KAAK,SAAS,EAAQ,MAAM,EAChE,IAAU,EAAE,oBAAoB,EAAM,KAAK,WAAW,EAAQ,QAAQ;AAE5E,QACE,kBAAA,GAAA,EAAA,UAAA,CACG,GACD,kBAAC,GAAD;EAAkB,MAAM,EAAM;EAAM,eAAe,MAAS,CAAC,KAAQ,GAAa;YAChF,kBAAC,GAAD;GAAyB,WAAU;GAAc,iBAAiB;aAAlE,CACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD;MAAe,WAAU;MAAS,eAAY;MAAS,CAAA;KACnD,CAAA,EACN,kBAAC,GAAD;KAAwB,WAAU;eAAlC,CACE,kBAAC,GAAD,EAAA,UAAwB,GAA8B,CAAA,EACtD,kBAAC,GAAD,EAAA,UAA8B,GAAsC,CAAA,CAC7C;OACrB;OACN,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;IAAQ,MAAK;IAAS,SAAQ;IAAU,SAAS;cAC9C,EAAE,4BAA4B,UAAU;IAClC,CAAA,EACT,kBAAC,GAAD;IAAQ,MAAK;IAAS,SAAS;cAC5B,IACG,EAAE,iCAAiC,iBAAiB,GACpD,EAAE,gCAAgC,eAAe;IAC9C,CAAA,CACc,EAAA,CAAA,CACD;;EACT,CAAA,CAClB,EAAA,CAAA"}
@@ -1,27 +1,30 @@
1
1
  import { extractQuotaExhaustedExtensions as e } from "@burdenoff/fe-libs/shared/utils";
2
2
  //#region src/bigconsole/utils/quota.ts
3
- var t = "bigconsole:quota-exhausted", n = new Set([
3
+ var t = "bigconsole:quota-exhausted", n = "QUOTA_NOT_PROVISIONED", r = {
4
+ billing: "/billing",
5
+ billingSetup: "/billing/overview"
6
+ }, i = new Set([
4
7
  "createbigconsoledashboard",
5
8
  "clonedashboard",
6
9
  "importdashboard",
7
10
  "importbigconsoledashboard",
8
11
  "instantiatetemplate",
9
12
  "createdashboardfromgeneration"
10
- ]), r = new Set(["createdashboardview"]), i = new Set([
13
+ ]), a = new Set(["createdashboardview"]), o = new Set([
11
14
  "generatedashboardfromprompt",
12
15
  "refinegenerateddashboard",
13
16
  "regeneratedashboard",
14
17
  "conversationaldashboardedit",
15
18
  "previewpromptanalysis"
16
- ]), a = new Set(["bigconsole.projects.count"]), o = new Set(["bigconsole.sessions.count"]), s = new Set(["bigconsole.commands.count"]);
17
- function c(e) {
19
+ ]), s = new Set(["bigconsole.projects.count"]), c = new Set(["bigconsole.sessions.count"]), l = new Set(["bigconsole.commands.count"]);
20
+ function u(e) {
18
21
  return typeof e == "object" && !!e;
19
22
  }
20
- function l(t) {
23
+ function d(t) {
21
24
  if (Array.isArray(t)) return e({ graphQLErrors: t });
22
- if (!c(t)) return null;
25
+ if (!u(t)) return null;
23
26
  if ("quotaResult" in t) {
24
- let e = l(t.quotaResult);
27
+ let e = d(t.quotaResult);
25
28
  if (e) return e;
26
29
  }
27
30
  if ("graphQLErrors" in t || "networkError" in t) {
@@ -29,39 +32,64 @@ function l(t) {
29
32
  if (n) return n;
30
33
  }
31
34
  if ("error" in t) {
32
- let e = l(t.error);
35
+ let e = d(t.error);
33
36
  if (e) return e;
34
37
  }
35
38
  if ("errors" in t && Array.isArray(t.errors)) {
36
39
  let n = e({ graphQLErrors: t.errors });
37
40
  if (n) return n;
38
41
  }
39
- if ("extensions" in t && c(t.extensions) && t.extensions.code === "QUOTA_EXHAUSTED") {
42
+ if ("extensions" in t && u(t.extensions) && t.extensions.code === "QUOTA_EXHAUSTED") {
40
43
  let n = e({ graphQLErrors: [t] });
41
44
  if (n) return n;
42
45
  }
43
46
  return null;
44
47
  }
45
- function u(e) {
46
- let t = /* @__PURE__ */ Error("QUOTA_EXHAUSTED");
48
+ function f(e, t = 0) {
49
+ if (t > 5 || !u(e)) return !1;
50
+ if (Array.isArray(e)) return e.some((e) => f(e, t + 1));
51
+ if (u(e.extensions)) {
52
+ let { code: t, errorCode: n } = e.extensions;
53
+ if (t === "QUOTA_NOT_PROVISIONED" || n === "QUOTA_NOT_PROVISIONED") return !0;
54
+ }
55
+ if (typeof e.bodyText == "string") try {
56
+ if (f(JSON.parse(e.bodyText), t + 1)) return !0;
57
+ } catch {}
58
+ return [
59
+ "quotaResult",
60
+ "error",
61
+ "errors",
62
+ "graphQLErrors",
63
+ "networkError",
64
+ "result"
65
+ ].some((n) => n in e && f(e[n], t + 1));
66
+ }
67
+ function p(e) {
68
+ let t = Error(f(e) ? n : "QUOTA_EXHAUSTED");
47
69
  return t.name = "BigConsoleQuotaError", Object.assign(t, { quotaResult: e });
48
70
  }
49
- function d(e) {
50
- return l(e) !== null;
71
+ function m(e) {
72
+ return d(e) !== null || f(e);
51
73
  }
52
- function f(e, t = "generic") {
53
- let c = e.quotaName?.toLowerCase(), l = e.field?.toLowerCase();
54
- return l && n.has(l) || c && a.has(c) ? "project" : l && r.has(l) || c && o.has(c) ? "session" : l && i.has(l) || c && s.has(c) ? "command" : t;
74
+ function h(e, t = "generic") {
75
+ let n = e.quotaName?.toLowerCase(), r = e.field?.toLowerCase();
76
+ return r && i.has(r) || n && s.has(n) ? "project" : r && a.has(r) || n && c.has(n) ? "session" : r && o.has(r) || n && l.has(n) ? "command" : t;
55
77
  }
56
- function p(e, n = "generic") {
57
- let r = l(e);
58
- return !r || typeof window > "u" ? !1 : (window.dispatchEvent(new CustomEvent(t, { detail: {
59
- kind: f(r, n),
78
+ function g(e, n = "generic") {
79
+ if (typeof window > "u") return !1;
80
+ if (f(e)) return window.dispatchEvent(new CustomEvent(t, { detail: { kind: "billing-setup" } })), !0;
81
+ let r = d(e);
82
+ return r ? (window.dispatchEvent(new CustomEvent(t, { detail: {
83
+ kind: h(r, n),
60
84
  extensions: r
61
- } })), !0);
85
+ } })), !0) : !1;
62
86
  }
63
- function m(e) {
87
+ function _(e) {
64
88
  switch (e) {
89
+ case "billing-setup": return {
90
+ title: "Set up billing to continue",
91
+ message: "Your organization does not have a billing account yet, so no BigConsole plan is active. Set up billing to activate the free plan, then try again."
92
+ };
65
93
  case "project": return {
66
94
  title: "Dashboard limit reached",
67
95
  message: "You have used all dashboards available on your current BigConsole plan. Upgrade to create, clone, import, or instantiate more dashboards."
@@ -81,6 +109,6 @@ function m(e) {
81
109
  }
82
110
  }
83
111
  //#endregion
84
- export { t as BIGCONSOLE_QUOTA_EXHAUSTED_EVENT, u as createBigConsoleQuotaError, m as getBigConsoleQuotaDialogContent, d as isBigConsoleQuotaError, p as reportBigConsoleQuotaError };
112
+ export { r as BIGCONSOLE_BILLING_PATHS, t as BIGCONSOLE_QUOTA_EXHAUSTED_EVENT, p as createBigConsoleQuotaError, _ as getBigConsoleQuotaDialogContent, m as isBigConsoleQuotaError, g as reportBigConsoleQuotaError };
85
113
 
86
114
  //# sourceMappingURL=quota.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"quota.js","names":[],"sources":["../../../src/bigconsole/utils/quota.ts"],"sourcesContent":["import { extractQuotaExhaustedExtensions, type QuotaExhaustedExtensions } from '@burdenoff/fe-libs/shared/utils';\n\nexport const BIGCONSOLE_QUOTA_EXHAUSTED_EVENT = 'bigconsole:quota-exhausted';\n\nexport type BigConsoleQuotaKind = 'project' | 'session' | 'command' | 'generic';\n\nexport interface BigConsoleQuotaExhaustedEventDetail {\n kind: BigConsoleQuotaKind;\n extensions: QuotaExhaustedExtensions;\n}\n\nconst PROJECT_FIELDS = new Set([\n 'createbigconsoledashboard',\n 'clonedashboard',\n 'importdashboard',\n 'importbigconsoledashboard',\n 'instantiatetemplate',\n 'createdashboardfromgeneration',\n]);\nconst SESSION_FIELDS = new Set(['createdashboardview']);\nconst COMMAND_FIELDS = new Set([\n 'generatedashboardfromprompt',\n 'refinegenerateddashboard',\n 'regeneratedashboard',\n 'conversationaldashboardedit',\n 'previewpromptanalysis',\n]);\nconst PROJECT_QUOTA_NAMES = new Set(['bigconsole.projects.count']);\nconst SESSION_QUOTA_NAMES = new Set(['bigconsole.sessions.count']);\nconst COMMAND_QUOTA_NAMES = new Set(['bigconsole.commands.count']);\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object';\n}\n\nexport function extractBigConsoleQuotaExtensions(value: unknown): QuotaExhaustedExtensions | null {\n if (Array.isArray(value)) {\n return extractQuotaExhaustedExtensions({ graphQLErrors: value });\n }\n\n if (!isObject(value)) {\n return null;\n }\n\n if ('quotaResult' in value) {\n const fromQuotaResult = extractBigConsoleQuotaExtensions(value.quotaResult);\n if (fromQuotaResult) return fromQuotaResult;\n }\n\n if ('graphQLErrors' in value || 'networkError' in value) {\n const fromApolloError = extractQuotaExhaustedExtensions(value);\n if (fromApolloError) return fromApolloError;\n }\n\n if ('error' in value) {\n const fromError = extractBigConsoleQuotaExtensions(value.error);\n if (fromError) return fromError;\n }\n\n if ('errors' in value && Array.isArray(value.errors)) {\n const fromErrors = extractQuotaExhaustedExtensions({ graphQLErrors: value.errors });\n if (fromErrors) return fromErrors;\n }\n\n if ('extensions' in value && isObject(value.extensions) && value.extensions.code === 'QUOTA_EXHAUSTED') {\n const fromGraphQLError = extractQuotaExhaustedExtensions({ graphQLErrors: [value] });\n if (fromGraphQLError) return fromGraphQLError;\n }\n\n return null;\n}\n\nexport function createBigConsoleQuotaError(value: unknown): Error {\n const error = new Error('QUOTA_EXHAUSTED');\n error.name = 'BigConsoleQuotaError';\n return Object.assign(error, { quotaResult: value });\n}\n\nexport function isBigConsoleQuotaError(value: unknown): boolean {\n return extractBigConsoleQuotaExtensions(value) !== null;\n}\n\nexport function inferBigConsoleQuotaKind(\n extensions: QuotaExhaustedExtensions,\n fallback: BigConsoleQuotaKind = 'generic'\n): BigConsoleQuotaKind {\n const quotaName = extensions.quotaName?.toLowerCase();\n const field = extensions.field?.toLowerCase();\n\n if ((field && PROJECT_FIELDS.has(field)) || (quotaName && PROJECT_QUOTA_NAMES.has(quotaName))) {\n return 'project';\n }\n\n if ((field && SESSION_FIELDS.has(field)) || (quotaName && SESSION_QUOTA_NAMES.has(quotaName))) {\n return 'session';\n }\n\n if ((field && COMMAND_FIELDS.has(field)) || (quotaName && COMMAND_QUOTA_NAMES.has(quotaName))) {\n return 'command';\n }\n\n return fallback;\n}\n\nexport function reportBigConsoleQuotaError(value: unknown, fallbackKind: BigConsoleQuotaKind = 'generic'): boolean {\n const extensions = extractBigConsoleQuotaExtensions(value);\n if (!extensions) return false;\n if (typeof window === 'undefined') return false;\n\n window.dispatchEvent(\n new CustomEvent<BigConsoleQuotaExhaustedEventDetail>(BIGCONSOLE_QUOTA_EXHAUSTED_EVENT, {\n detail: {\n kind: inferBigConsoleQuotaKind(extensions, fallbackKind),\n extensions,\n },\n })\n );\n\n return true;\n}\n\nexport function getBigConsoleQuotaDialogContent(kind: BigConsoleQuotaKind): {\n title: string;\n message: string;\n} {\n switch (kind) {\n case 'project':\n return {\n title: 'Dashboard limit reached',\n message:\n 'You have used all dashboards available on your current BigConsole plan. Upgrade to create, clone, import, or instantiate more dashboards.',\n };\n case 'session':\n return {\n title: 'Saved view limit reached',\n message:\n 'You have used all saved views available on your current BigConsole plan. Upgrade to save more dashboard views.',\n };\n case 'command':\n return {\n title: 'AI command limit reached',\n message:\n 'You have used all AI commands available on your current BigConsole plan. Upgrade to generate and refine more dashboards.',\n };\n case 'generic':\n default:\n return {\n title: 'Quota reached',\n message: 'You have reached a BigConsole usage limit on your current plan. Upgrade to continue.',\n };\n }\n}\n"],"mappings":";;AAEA,IAAa,IAAmC,8BAS1C,IAAiB,IAAI,IAAI;CAC7B;CACA;CACA;CACA;CACA;CACA;CACD,CAAC,EACI,IAAiB,IAAI,IAAI,CAAC,sBAAsB,CAAC,EACjD,IAAiB,IAAI,IAAI;CAC7B;CACA;CACA;CACA;CACA;CACD,CAAC,EACI,IAAsB,IAAI,IAAI,CAAC,4BAA4B,CAAC,EAC5D,IAAsB,IAAI,IAAI,CAAC,4BAA4B,CAAC,EAC5D,IAAsB,IAAI,IAAI,CAAC,4BAA4B,CAAC;AAElE,SAAS,EAAS,GAAkD;AAClE,QAAyB,OAAO,KAAU,cAAnC;;AAGT,SAAgB,EAAiC,GAAiD;AAChG,KAAI,MAAM,QAAQ,EAAM,CACtB,QAAO,EAAgC,EAAE,eAAe,GAAO,CAAC;AAGlE,KAAI,CAAC,EAAS,EAAM,CAClB,QAAO;AAGT,KAAI,iBAAiB,GAAO;EAC1B,IAAM,IAAkB,EAAiC,EAAM,YAAY;AAC3E,MAAI,EAAiB,QAAO;;AAG9B,KAAI,mBAAmB,KAAS,kBAAkB,GAAO;EACvD,IAAM,IAAkB,EAAgC,EAAM;AAC9D,MAAI,EAAiB,QAAO;;AAG9B,KAAI,WAAW,GAAO;EACpB,IAAM,IAAY,EAAiC,EAAM,MAAM;AAC/D,MAAI,EAAW,QAAO;;AAGxB,KAAI,YAAY,KAAS,MAAM,QAAQ,EAAM,OAAO,EAAE;EACpD,IAAM,IAAa,EAAgC,EAAE,eAAe,EAAM,QAAQ,CAAC;AACnF,MAAI,EAAY,QAAO;;AAGzB,KAAI,gBAAgB,KAAS,EAAS,EAAM,WAAW,IAAI,EAAM,WAAW,SAAS,mBAAmB;EACtG,IAAM,IAAmB,EAAgC,EAAE,eAAe,CAAC,EAAM,EAAE,CAAC;AACpF,MAAI,EAAkB,QAAO;;AAG/B,QAAO;;AAGT,SAAgB,EAA2B,GAAuB;CAChE,IAAM,IAAQ,gBAAI,MAAM,kBAAkB;AAE1C,QADA,EAAM,OAAO,wBACN,OAAO,OAAO,GAAO,EAAE,aAAa,GAAO,CAAC;;AAGrD,SAAgB,EAAuB,GAAyB;AAC9D,QAAO,EAAiC,EAAM,KAAK;;AAGrD,SAAgB,EACd,GACA,IAAgC,WACX;CACrB,IAAM,IAAY,EAAW,WAAW,aAAa,EAC/C,IAAQ,EAAW,OAAO,aAAa;AAc7C,QAZK,KAAS,EAAe,IAAI,EAAM,IAAM,KAAa,EAAoB,IAAI,EAAU,GACnF,YAGJ,KAAS,EAAe,IAAI,EAAM,IAAM,KAAa,EAAoB,IAAI,EAAU,GACnF,YAGJ,KAAS,EAAe,IAAI,EAAM,IAAM,KAAa,EAAoB,IAAI,EAAU,GACnF,YAGF;;AAGT,SAAgB,EAA2B,GAAgB,IAAoC,WAAoB;CACjH,IAAM,IAAa,EAAiC,EAAM;AAa1D,QAZI,CAAC,KACD,OAAO,SAAW,MAAoB,MAE1C,OAAO,cACL,IAAI,YAAiD,GAAkC,EACrF,QAAQ;EACN,MAAM,EAAyB,GAAY,EAAa;EACxD;EACD,EACF,CAAC,CACH,EAEM;;AAGT,SAAgB,EAAgC,GAG9C;AACA,SAAQ,GAAR;EACE,KAAK,UACH,QAAO;GACL,OAAO;GACP,SACE;GACH;EACH,KAAK,UACH,QAAO;GACL,OAAO;GACP,SACE;GACH;EACH,KAAK,UACH,QAAO;GACL,OAAO;GACP,SACE;GACH;EAEH,QACE,QAAO;GACL,OAAO;GACP,SAAS;GACV"}
1
+ {"version":3,"file":"quota.js","names":[],"sources":["../../../src/bigconsole/utils/quota.ts"],"sourcesContent":["import { extractQuotaExhaustedExtensions, type QuotaExhaustedExtensions } from '@burdenoff/fe-libs/shared/utils';\n\nexport const BIGCONSOLE_QUOTA_EXHAUSTED_EVENT = 'bigconsole:quota-exhausted';\n\n/**\n * `billing-setup` is not a spent quota: the gateway answered `QUOTA_NOT_PROVISIONED`\n * because the organisation has no billing account yet, so no free plan covers the\n * metered mutation. It is fixed by setting billing up, not by upgrading (BOFF-7913).\n */\nexport type BigConsoleQuotaKind = 'project' | 'session' | 'command' | 'generic' | 'billing-setup';\n\nexport interface BigConsoleQuotaExhaustedEventDetail {\n kind: BigConsoleQuotaKind;\n /** Present for exhausted quotas; a not-provisioned refusal carries no usage figures. */\n extensions?: QuotaExhaustedExtensions;\n}\n\nexport const QUOTA_NOT_PROVISIONED_CODE = 'QUOTA_NOT_PROVISIONED';\n\n/** Where each dialog kind sends the user. */\nexport const BIGCONSOLE_BILLING_PATHS: Record<'billing' | 'billingSetup', string> = {\n billing: '/billing',\n billingSetup: '/billing/overview',\n};\n\nconst PROJECT_FIELDS = new Set([\n 'createbigconsoledashboard',\n 'clonedashboard',\n 'importdashboard',\n 'importbigconsoledashboard',\n 'instantiatetemplate',\n 'createdashboardfromgeneration',\n]);\nconst SESSION_FIELDS = new Set(['createdashboardview']);\nconst COMMAND_FIELDS = new Set([\n 'generatedashboardfromprompt',\n 'refinegenerateddashboard',\n 'regeneratedashboard',\n 'conversationaldashboardedit',\n 'previewpromptanalysis',\n]);\nconst PROJECT_QUOTA_NAMES = new Set(['bigconsole.projects.count']);\nconst SESSION_QUOTA_NAMES = new Set(['bigconsole.sessions.count']);\nconst COMMAND_QUOTA_NAMES = new Set(['bigconsole.commands.count']);\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object';\n}\n\nexport function extractBigConsoleQuotaExtensions(value: unknown): QuotaExhaustedExtensions | null {\n if (Array.isArray(value)) {\n return extractQuotaExhaustedExtensions({ graphQLErrors: value });\n }\n\n if (!isObject(value)) {\n return null;\n }\n\n if ('quotaResult' in value) {\n const fromQuotaResult = extractBigConsoleQuotaExtensions(value.quotaResult);\n if (fromQuotaResult) return fromQuotaResult;\n }\n\n if ('graphQLErrors' in value || 'networkError' in value) {\n const fromApolloError = extractQuotaExhaustedExtensions(value);\n if (fromApolloError) return fromApolloError;\n }\n\n if ('error' in value) {\n const fromError = extractBigConsoleQuotaExtensions(value.error);\n if (fromError) return fromError;\n }\n\n if ('errors' in value && Array.isArray(value.errors)) {\n const fromErrors = extractQuotaExhaustedExtensions({ graphQLErrors: value.errors });\n if (fromErrors) return fromErrors;\n }\n\n if ('extensions' in value && isObject(value.extensions) && value.extensions.code === 'QUOTA_EXHAUSTED') {\n const fromGraphQLError = extractQuotaExhaustedExtensions({ graphQLErrors: [value] });\n if (fromGraphQLError) return fromGraphQLError;\n }\n\n return null;\n}\n\n/**\n * Whether a GraphQL error code of `QUOTA_NOT_PROVISIONED` is reachable from `value`.\n *\n * Walks the same shapes as {@link extractBigConsoleQuotaExtensions} — Apollo results\n * (`error`, `errors`), legacy Apollo errors (`graphQLErrors`, `networkError.result`),\n * wrapped hook errors (`quotaResult`) and a bare GraphQL error — plus the JSON\n * `bodyText` of an Apollo `ServerError`, which is how a 429 whose body was not parsed\n * as a GraphQL response reaches the caller. An `Error.message` is deliberately not\n * matched: prose is not a code.\n */\nexport function isBigConsoleBillingNotProvisionedError(value: unknown, depth = 0): boolean {\n if (depth > 5 || !isObject(value)) return false;\n if (Array.isArray(value)) {\n return value.some((entry) => isBigConsoleBillingNotProvisionedError(entry, depth + 1));\n }\n\n if (isObject(value.extensions)) {\n const { code, errorCode } = value.extensions;\n if (code === QUOTA_NOT_PROVISIONED_CODE || errorCode === QUOTA_NOT_PROVISIONED_CODE) return true;\n }\n\n if (typeof value.bodyText === 'string') {\n // Parsed and walked structurally, so a diagnostic string that merely\n // mentions the code (e.g. in a `details` field) does not match.\n try {\n const parsed: unknown = JSON.parse(value.bodyText);\n if (isBigConsoleBillingNotProvisionedError(parsed, depth + 1)) return true;\n } catch {\n // Not JSON: not a structured GraphQL error response.\n }\n }\n\n return ['quotaResult', 'error', 'errors', 'graphQLErrors', 'networkError', 'result'].some(\n (key) => key in value && isBigConsoleBillingNotProvisionedError(value[key], depth + 1)\n );\n}\n\nexport function createBigConsoleQuotaError(value: unknown): Error {\n const error = new Error(\n isBigConsoleBillingNotProvisionedError(value) ? QUOTA_NOT_PROVISIONED_CODE : 'QUOTA_EXHAUSTED'\n );\n error.name = 'BigConsoleQuotaError';\n return Object.assign(error, { quotaResult: value });\n}\n\n/** An exhausted quota, or a quota that was never provisioned because billing is not set up. */\nexport function isBigConsoleQuotaError(value: unknown): boolean {\n return extractBigConsoleQuotaExtensions(value) !== null || isBigConsoleBillingNotProvisionedError(value);\n}\n\nexport function inferBigConsoleQuotaKind(\n extensions: QuotaExhaustedExtensions,\n fallback: BigConsoleQuotaKind = 'generic'\n): BigConsoleQuotaKind {\n const quotaName = extensions.quotaName?.toLowerCase();\n const field = extensions.field?.toLowerCase();\n\n if ((field && PROJECT_FIELDS.has(field)) || (quotaName && PROJECT_QUOTA_NAMES.has(quotaName))) {\n return 'project';\n }\n\n if ((field && SESSION_FIELDS.has(field)) || (quotaName && SESSION_QUOTA_NAMES.has(quotaName))) {\n return 'session';\n }\n\n if ((field && COMMAND_FIELDS.has(field)) || (quotaName && COMMAND_QUOTA_NAMES.has(quotaName))) {\n return 'command';\n }\n\n return fallback;\n}\n\nexport function reportBigConsoleQuotaError(value: unknown, fallbackKind: BigConsoleQuotaKind = 'generic'): boolean {\n if (typeof window === 'undefined') return false;\n\n // Checked first: an organisation without billing has no plan at all, so an\n // \"upgrade\" message would send them to buy something that would not help.\n if (isBigConsoleBillingNotProvisionedError(value)) {\n window.dispatchEvent(\n new CustomEvent<BigConsoleQuotaExhaustedEventDetail>(BIGCONSOLE_QUOTA_EXHAUSTED_EVENT, {\n detail: { kind: 'billing-setup' },\n })\n );\n return true;\n }\n\n const extensions = extractBigConsoleQuotaExtensions(value);\n if (!extensions) return false;\n\n window.dispatchEvent(\n new CustomEvent<BigConsoleQuotaExhaustedEventDetail>(BIGCONSOLE_QUOTA_EXHAUSTED_EVENT, {\n detail: {\n kind: inferBigConsoleQuotaKind(extensions, fallbackKind),\n extensions,\n },\n })\n );\n\n return true;\n}\n\nexport function getBigConsoleQuotaDialogContent(kind: BigConsoleQuotaKind): {\n title: string;\n message: string;\n} {\n switch (kind) {\n case 'billing-setup':\n return {\n title: 'Set up billing to continue',\n message:\n 'Your organization does not have a billing account yet, so no BigConsole plan is active. Set up billing to activate the free plan, then try again.',\n };\n case 'project':\n return {\n title: 'Dashboard limit reached',\n message:\n 'You have used all dashboards available on your current BigConsole plan. Upgrade to create, clone, import, or instantiate more dashboards.',\n };\n case 'session':\n return {\n title: 'Saved view limit reached',\n message:\n 'You have used all saved views available on your current BigConsole plan. Upgrade to save more dashboard views.',\n };\n case 'command':\n return {\n title: 'AI command limit reached',\n message:\n 'You have used all AI commands available on your current BigConsole plan. Upgrade to generate and refine more dashboards.',\n };\n case 'generic':\n default:\n return {\n title: 'Quota reached',\n message: 'You have reached a BigConsole usage limit on your current plan. Upgrade to continue.',\n };\n }\n}\n"],"mappings":"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"}
@@ -50,7 +50,6 @@ var i = t`
50
50
  contentReviewState
51
51
  presentationTheme
52
52
  showDashboardTitle
53
- showBrandingFooter
54
53
  activePublicationId
55
54
  locatorPublicId
56
55
  fingerprint