@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":"ImportDataSinkDialog.js","names":[],"sources":["../../../../src/bigconsole/components/datasink/ImportDataSinkDialog.tsx"],"sourcesContent":["/**\n * ImportDataSinkDialog Component\n *\n * Modal dialog for importing a DataSink from JSON file.\n * Supports both creating new DataSinks and updating existing ones.\n */\n\nimport { type FC, memo, useState, useCallback, useEffect, useRef } from 'react';\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@burdenoff/fe-libs/ui';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Input } from '@burdenoff/fe-libs/ui';\nimport { Label } from '@burdenoff/fe-libs/ui';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport { Badge } from '@burdenoff/fe-libs/ui';\nimport { Upload, FileJson, Check, XCircle, ArrowLeft, RefreshCw, Info } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useImportDataSink, type ImportDataSinkOptions } from '../../hooks';\nimport { useBigConsoleAnalytics } from '../../hooks/useBigConsoleAnalytics';\nimport { Section, SectionStack } from '../common';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ImportDataSinkDialogProps {\n isOpen: boolean;\n onClose: () => void;\n onSuccess?: (dataSinkId: string, dataSinkName: string | null, originalUpdated: boolean) => void;\n}\n\ntype ImportStep = 'select' | 'preview' | 'importing' | 'complete';\n\n/**\n * Wizard steps. The dialog previously moved between three screens with no\n * indicator at all, so a user could not tell where they were.\n */\nconst WIZARD_STEPS: { id: ImportStep; label: string }[] = [\n { id: 'select', label: 'Choose file' },\n { id: 'preview', label: 'Review' },\n { id: 'complete', label: 'Done' },\n];\n\n/** Index of the wizard step currently on screen ('importing' still shows Review). */\nfunction wizardIndex(step: ImportStep): number {\n if (step === 'select') return 0;\n if (step === 'complete') return 2;\n return 1;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const ImportDataSinkDialog: FC<ImportDataSinkDialogProps> = memo(function ImportDataSinkDialog({\n isOpen,\n onClose,\n onSuccess,\n}) {\n const { t } = useI18n();\n const [step, setStep] = useState<ImportStep>('select');\n const [, setSelectedFile] = useState<File | null>(null);\n const [fileContent, setFileContent] = useState<string | null>(null);\n const [nameOverride, setNameOverride] = useState('');\n const [keyOverride, setKeyOverride] = useState('');\n\n const fileInputRef = useRef<HTMLInputElement>(null);\n\n const {\n loading,\n error,\n validationResult,\n importResult,\n validateImport,\n importDataSink,\n extractDataSinkId,\n clearState,\n } = useImportDataSink();\n const emitBigConsoleEvent = useBigConsoleAnalytics();\n\n // Reset state when dialog opens\n useEffect(() => {\n if (isOpen) {\n setStep('select');\n setSelectedFile(null);\n setFileContent(null);\n setNameOverride('');\n setKeyOverride('');\n clearState();\n }\n }, [isOpen, clearState]);\n\n // Handle file selection\n const handleFileSelect = useCallback(\n async (e: React.ChangeEvent<HTMLInputElement>) => {\n const file = e.target.files?.[0];\n if (!file) return;\n\n if (!file.name.endsWith('.json')) {\n alert(t('bigconsole.dataSinks.import.selectJsonFile', 'Please select a JSON file'));\n return;\n }\n\n setSelectedFile(file);\n\n const reader = new FileReader();\n reader.onload = async (event) => {\n const content = event.target?.result as string;\n setFileContent(content);\n\n const result = validateImport(content);\n if (result.isValid && result.dataSinkInfo) {\n setStep('preview');\n setNameOverride(result.dataSinkInfo.name || '');\n setKeyOverride(result.dataSinkInfo.key || '');\n } else {\n setStep('preview');\n }\n };\n reader.onerror = () => {\n alert(t('bigconsole.dataSinks.import.readFileError', 'Failed to read file'));\n };\n reader.readAsText(file);\n },\n [validateImport, t]\n );\n\n // Handle file drop\n const handleDrop = useCallback((e: React.DragEvent) => {\n e.preventDefault();\n const file = e.dataTransfer.files?.[0];\n if (file) {\n const dt = new DataTransfer();\n dt.items.add(file);\n if (fileInputRef.current) {\n fileInputRef.current.files = dt.files;\n fileInputRef.current.dispatchEvent(new Event('change', { bubbles: true }));\n }\n }\n }, []);\n\n // Handle import\n const handleImport = useCallback(async () => {\n if (!fileContent) return;\n\n setStep('importing');\n\n const options: ImportDataSinkOptions = {\n name: nameOverride.trim() || undefined,\n key: keyOverride.trim() || undefined,\n };\n\n emitBigConsoleEvent('bigconsole.data_sink.import_clicked', { source: 'import' });\n\n const result = await importDataSink(fileContent, options);\n\n if (result?.success && result.dataSink) {\n setStep('complete');\n emitBigConsoleEvent('bigconsole.data_sink.imported', {\n dataSinkId: result.dataSink.id,\n source: 'import',\n originalUpdated: result.originalUpdated ?? false,\n });\n onSuccess?.(result.dataSink.id, result.dataSink.name, result.originalUpdated ?? false);\n } else {\n setStep('preview');\n }\n }, [emitBigConsoleEvent, fileContent, importDataSink, keyOverride, nameOverride, onSuccess]);\n\n const canClose = !loading && step !== 'importing';\n const dataSinkId = fileContent ? extractDataSinkId(fileContent) : null;\n const currentStepIndex = wizardIndex(step);\n\n return (\n <Dialog open={isOpen} onOpenChange={(open) => !open && canClose && onClose()}>\n <DialogContent className=\"flex max-h-[85vh] flex-col sm:max-w-lg\">\n <DialogHeader>\n <div className=\"flex items-center gap-3\">\n <div className=\"flex size-10 items-center justify-center rounded-card bg-bg-accentSubtle\">\n <Upload className=\"size-5 text-text-link\" />\n </div>\n <DialogTitle>{t('bigconsole.dataSinks.import.title', 'Import DataSink')}</DialogTitle>\n </div>\n <DialogDescription>\n {t(\n 'bigconsole.dataSinks.import.description',\n 'Upload a DataSink export file to restore it into this workspace.'\n )}\n </DialogDescription>\n </DialogHeader>\n\n {/* Step indicator — structural strip so the wizard reads as a sequence. */}\n <ol className=\"flex flex-wrap items-center gap-x-icon-gap gap-y-1 text-xs\" aria-label=\"Import progress\">\n {WIZARD_STEPS.map((wizardStep, index) => (\n <li\n key={wizardStep.id}\n aria-current={index === currentStepIndex ? 'step' : undefined}\n className={`flex items-center gap-icon-gap ${\n index === currentStepIndex ? 'font-semibold text-text-primary' : 'text-text-muted'\n }`}\n >\n <span\n className={`inline-flex size-5 items-center justify-center rounded-pill text-[10px] ${\n index <= currentStepIndex ? 'bg-action-primary-bg text-action-primary-text' : 'bg-bg-sunken'\n }`}\n >\n {index + 1}\n </span>\n {wizardStep.label}\n {index < WIZARD_STEPS.length - 1 && (\n <span aria-hidden=\"true\" className=\"text-text-muted\">\n ›\n </span>\n )}\n </li>\n ))}\n </ol>\n\n <div className=\"min-w-0 flex-1 overflow-y-auto py-4\">\n <SectionStack>\n {/* Step 1: File Selection */}\n {step === 'select' && (\n <Section\n title=\"Choose an export file\"\n description={t(\n 'bigconsole.dataSinks.import.supportedFilesHint',\n 'Only .json files exported from BigConsole are supported'\n )}\n headingLevel={3}\n bodyClassName=\"mt-form-gap\"\n >\n <div\n onDragOver={(e) => e.preventDefault()}\n onDrop={handleDrop}\n className=\"rounded-card border-2 border-dashed border-[var(--color-border-default)] p-8 text-center hover:border-[var(--color-action-primary-bg)] motion-safe:transition-colors\"\n >\n <FileJson className=\"mx-auto mb-4 size-12 text-text-secondary\" />\n <p className=\"mb-2 text-sm\">\n {t('bigconsole.dataSinks.import.dropzoneText', 'Drag and drop a DataSink export file here')}\n </p>\n <p className=\"mb-4 text-xs text-text-secondary\">{t('bigconsole.dataSinks.import.or', 'or')}</p>\n <input ref={fileInputRef} type=\"file\" accept=\".json\" onChange={handleFileSelect} className=\"hidden\" />\n <Button onClick={() => fileInputRef.current?.click()} disabled={loading}>\n {t('bigconsole.dataSinks.import.browseFiles', 'Browse Files')}\n </Button>\n </div>\n </Section>\n )}\n\n {/* Step 2: Preview & Options */}\n {(step === 'preview' || step === 'importing') && validationResult && (\n <>\n {/* What is in the file — supporting detail. */}\n {validationResult.dataSinkInfo && (\n <Section\n emphasis=\"muted\"\n padding=\"compact\"\n headingLevel={3}\n title=\"File contents\"\n description=\"What this export file describes.\"\n bodyClassName=\"space-y-2\"\n >\n <div className=\"flex items-center justify-between gap-icon-gap\">\n <span className=\"text-sm text-text-secondary\">\n {t('bigconsole.dataSinks.import.nameLabel', 'Name')}\n </span>\n <span className=\"min-w-0 truncate text-sm font-medium\">\n {validationResult.dataSinkInfo.name || t('bigconsole.dataSinks.import.unnamed', 'Unnamed')}\n </span>\n </div>\n <div className=\"flex items-center justify-between gap-icon-gap\">\n <span className=\"text-sm text-text-secondary\">\n {t('bigconsole.dataSinks.import.keyLabel', 'Key')}\n </span>\n <span className=\"min-w-0 truncate font-mono text-xs\">{validationResult.dataSinkInfo.key}</span>\n </div>\n <div className=\"flex items-center justify-between gap-icon-gap\">\n <span className=\"text-sm text-text-secondary\">\n {t('bigconsole.dataSinks.import.statusLabel', 'Status')}\n </span>\n <Badge variant=\"secondary\">{validationResult.dataSinkInfo.status}</Badge>\n </div>\n <div className=\"flex items-center justify-between gap-icon-gap\">\n <span className=\"text-sm text-text-secondary\">\n {t('bigconsole.dataSinks.import.setupStateLabel', 'Setup State')}\n </span>\n <Badge variant=\"outline\">{validationResult.dataSinkInfo.setupState}</Badge>\n </div>\n {validationResult.dataSinkInfo.exportedFrom && (\n <div className=\"flex items-center justify-between gap-icon-gap\">\n <span className=\"text-sm text-text-secondary\">\n {t('bigconsole.dataSinks.import.sourceIdLabel', 'Source ID')}\n </span>\n <span className=\"min-w-0 max-w-[180px] truncate font-mono text-xs text-text-secondary\">\n {validationResult.dataSinkInfo.exportedFrom}\n </span>\n </div>\n )}\n <div className=\"flex items-center justify-between gap-icon-gap\">\n <span className=\"text-sm text-text-secondary\">\n {t('bigconsole.dataSinks.import.schemaVersionLabel', 'Schema Version')}\n </span>\n <span className=\"text-xs text-text-secondary\">{validationResult.dataSinkInfo.schemaVersion}</span>\n </div>\n </Section>\n )}\n\n {/* Validation Errors */}\n {validationResult.errors.length > 0 && (\n <Section\n tone=\"danger\"\n padding=\"compact\"\n headingLevel={3}\n title={t('bigconsole.dataSinks.import.errorsHeading', 'Errors ({{count}})', {\n count: validationResult.errors.length,\n })}\n icon={<XCircle className=\"size-4\" />}\n >\n <ul className=\"space-y-1 text-xs text-status-error-text\">\n {validationResult.errors.map((err, idx) => (\n <li key={idx}>\n <span className=\"font-mono\">[{err.code}]</span> {err.message}\n </li>\n ))}\n </ul>\n </Section>\n )}\n\n {/* Import Options */}\n {validationResult.isValid && (\n <Section\n title={t('bigconsole.dataSinks.import.optionsHeading', 'Import Options')}\n description=\"Rename the sink or give it a different key before importing.\"\n headingLevel={3}\n bodyClassName=\"space-y-form-gap\"\n >\n <Section\n tone=\"info\"\n emphasis=\"muted\"\n padding=\"compact\"\n headingLevel={4}\n icon={<Info className=\"size-4\" />}\n title={\n dataSinkId\n ? t(\n 'bigconsole.dataSinks.import.checkExistingHeading',\n 'Import will check for existing DataSink'\n )\n : t('bigconsole.dataSinks.import.newWillBeCreatedHeading', 'A new DataSink will be created')\n }\n >\n <div className=\"text-xs text-[var(--color-status-info-text)]\">\n {dataSinkId ? (\n <>\n <p>\n {t('bigconsole.dataSinks.import.ifOriginalExistsPrefix', 'If the original DataSink (ID:')}{' '}\n <code className=\"rounded-input bg-bg-surface px-1 font-mono\">\n {dataSinkId.slice(0, 8)}...\n </code>\n {t('bigconsole.dataSinks.import.ifOriginalExistsSuffix', ') exists:')}\n </p>\n <ul className=\"mt-1 list-inside list-disc space-y-0.5\">\n <li>\n {t(\n 'bigconsole.dataSinks.import.withChangesUpdate',\n 'With changes → Update the existing DataSink'\n )}\n </li>\n <li>\n {t(\n 'bigconsole.dataSinks.import.noChangesMessage',\n 'No changes → Show \"already exists\" message'\n )}\n </li>\n </ul>\n <p className=\"mt-1\">\n {t(\n 'bigconsole.dataSinks.import.ifNotExistsCreate',\n \"If original doesn't exist → Create a new DataSink\"\n )}\n </p>\n </>\n ) : (\n <p>\n {t(\n 'bigconsole.dataSinks.import.newWillBeCreatedBody',\n 'Import as a new DataSink with the specified configuration.'\n )}\n </p>\n )}\n </div>\n </Section>\n\n <div className=\"space-y-2\">\n <Label htmlFor=\"import-name\">\n {t('bigconsole.dataSinks.import.nameFieldLabel', 'DataSink Name')}\n </Label>\n <Input\n id=\"import-name\"\n type=\"text\"\n value={nameOverride}\n onChange={(e) => setNameOverride(e.target.value)}\n placeholder={\n validationResult.dataSinkInfo?.name ||\n t('bigconsole.dataSinks.import.namePlaceholder', 'DataSink name')\n }\n disabled={step === 'importing'}\n />\n </div>\n\n <div className=\"space-y-2\">\n <Label htmlFor=\"import-key\">\n {t('bigconsole.dataSinks.import.keyFieldLabel', 'DataSink Key')}\n </Label>\n <Input\n id=\"import-key\"\n type=\"text\"\n value={keyOverride}\n onChange={(e) => setKeyOverride(e.target.value)}\n placeholder={\n validationResult.dataSinkInfo?.key ||\n t('bigconsole.dataSinks.import.keyPlaceholder', 'datasink-key')\n }\n disabled={step === 'importing'}\n />\n <p className=\"text-xs text-text-secondary\">\n {t(\n 'bigconsole.dataSinks.import.keyHelp',\n 'Use a unique key to avoid conflicts with existing DataSinks.'\n )}\n </p>\n </div>\n </Section>\n )}\n </>\n )}\n\n {/* Step 3: Complete */}\n {step === 'complete' && importResult?.success && (\n <>\n {/* Already Exists - No Changes */}\n {importResult.alreadyExists && (\n <Section\n tone=\"info\"\n headingLevel={3}\n title={t('bigconsole.dataSinks.import.alreadyExistsTitle', 'DataSink Already Exists')}\n icon={<Info className=\"h-icon-sm w-icon-sm\" />}\n >\n <p className=\"text-sm text-text-secondary\">\n {t(\n 'bigconsole.dataSinks.import.alreadyExistsBody',\n 'DataSink \"{{name}}\" already exists with the same data. No changes were made.',\n { name: importResult.dataSink?.name || importResult.dataSink?.key || '' }\n )}\n </p>\n </Section>\n )}\n\n {/* Updated Existing */}\n {importResult.originalUpdated && !importResult.alreadyExists && (\n <Section\n tone=\"success\"\n headingLevel={3}\n title={t('bigconsole.dataSinks.import.updatedTitle', 'DataSink Updated!')}\n icon={<RefreshCw className=\"h-icon-sm w-icon-sm\" />}\n >\n <p className=\"text-sm text-text-secondary\">\n {t(\n 'bigconsole.dataSinks.import.updatedBody',\n 'DataSink \"{{name}}\" has been updated with the imported data.',\n { name: importResult.dataSink?.name || importResult.dataSink?.key || '' }\n )}\n </p>\n </Section>\n )}\n\n {/* Created New */}\n {!importResult.originalUpdated && !importResult.alreadyExists && (\n <Section\n tone=\"success\"\n headingLevel={3}\n title={t('bigconsole.dataSinks.import.successTitle', 'Import Successful!')}\n icon={<Check className=\"h-icon-sm w-icon-sm\" />}\n >\n <p className=\"text-sm text-text-secondary\">\n {t('bigconsole.dataSinks.import.successBody', 'DataSink \"{{name}}\" has been created.', {\n name: importResult.dataSink?.name || importResult.dataSink?.key || '',\n })}\n </p>\n </Section>\n )}\n </>\n )}\n\n {/* Error */}\n {error && (\n <Section tone=\"danger\" padding=\"compact\" headingLevel={3} title=\"Import failed\">\n <p className=\"text-sm text-status-error-text\">{error.message}</p>\n </Section>\n )}\n\n {importResult && !importResult.success && importResult.errorMessage && (\n <Section tone=\"danger\" padding=\"compact\" headingLevel={3} title=\"Import failed\">\n <p className=\"text-sm text-status-error-text\">{importResult.errorMessage}</p>\n </Section>\n )}\n </SectionStack>\n </div>\n\n <DialogFooter className=\"flex flex-col-reverse gap-control-gap sm:flex-row sm:justify-between\">\n {step === 'preview' ? (\n <Button\n variant=\"ghost\"\n onClick={() => {\n setStep('select');\n setSelectedFile(null);\n setFileContent(null);\n clearState();\n }}\n disabled={loading}\n >\n <ArrowLeft className=\"size-4\" />\n {t('bigconsole.dataSinks.import.back', 'Back')}\n </Button>\n ) : (\n <span aria-hidden=\"true\" className=\"hidden sm:block\" />\n )}\n\n <div className=\"flex flex-wrap items-center gap-2\">\n {step !== 'complete' && (\n <Button variant=\"outline\" onClick={onClose} disabled={!canClose}>\n {t('bigconsole.dataSinks.import.cancel', 'Cancel')}\n </Button>\n )}\n\n {step === 'preview' && validationResult?.isValid && (\n <Button onClick={handleImport} disabled={loading}>\n <Upload className=\"size-4\" />\n {t('bigconsole.dataSinks.import.importButton', 'Import DataSink')}\n </Button>\n )}\n\n {step === 'importing' && (\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <Spinner className=\"size-4\" />\n {t('bigconsole.dataSinks.import.importing', 'Importing...')}\n </div>\n )}\n\n {step === 'complete' && <Button onClick={onClose}>{t('bigconsole.dataSinks.import.done', 'Done')}</Button>}\n </div>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n});\n\nexport default ImportDataSinkDialog;\n"],"mappings":"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"}
1
+ {"version":3,"file":"ImportDataSinkDialog.js","names":[],"sources":["../../../../src/bigconsole/components/datasink/ImportDataSinkDialog.tsx"],"sourcesContent":["/**\n * ImportDataSinkDialog Component\n *\n * Modal dialog for importing a DataSink from JSON file.\n * Supports both creating new DataSinks and updating existing ones.\n */\n\nimport { type FC, memo, useState, useCallback, useEffect, useRef } from 'react';\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@burdenoff/fe-libs/ui';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Input } from '@burdenoff/fe-libs/ui';\nimport { Label } from '@burdenoff/fe-libs/ui';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport { Badge } from '@burdenoff/fe-libs/ui';\nimport { Upload, FileJson, Check, XCircle, ArrowLeft, RefreshCw, Info } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useImportDataSink, type ImportDataSinkOptions } from '../../hooks';\nimport { useBigConsoleAnalytics } from '../../hooks/useBigConsoleAnalytics';\nimport { Section, SectionStack } from '../common';\nimport { reportBigConsoleQuotaError } from '../../utils/quota';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ImportDataSinkDialogProps {\n isOpen: boolean;\n onClose: () => void;\n onSuccess?: (dataSinkId: string, dataSinkName: string | null, originalUpdated: boolean) => void;\n}\n\ntype ImportStep = 'select' | 'preview' | 'importing' | 'complete';\n\n/**\n * Wizard steps. The dialog previously moved between three screens with no\n * indicator at all, so a user could not tell where they were.\n */\nconst WIZARD_STEPS: { id: ImportStep; label: string }[] = [\n { id: 'select', label: 'Choose file' },\n { id: 'preview', label: 'Review' },\n { id: 'complete', label: 'Done' },\n];\n\n/** Index of the wizard step currently on screen ('importing' still shows Review). */\nfunction wizardIndex(step: ImportStep): number {\n if (step === 'select') return 0;\n if (step === 'complete') return 2;\n return 1;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const ImportDataSinkDialog: FC<ImportDataSinkDialogProps> = memo(function ImportDataSinkDialog({\n isOpen,\n onClose,\n onSuccess,\n}) {\n const { t } = useI18n();\n const [step, setStep] = useState<ImportStep>('select');\n const [, setSelectedFile] = useState<File | null>(null);\n const [fileContent, setFileContent] = useState<string | null>(null);\n const [nameOverride, setNameOverride] = useState('');\n const [keyOverride, setKeyOverride] = useState('');\n\n const fileInputRef = useRef<HTMLInputElement>(null);\n\n const {\n loading,\n error,\n validationResult,\n importResult,\n validateImport,\n importDataSink,\n extractDataSinkId,\n clearState,\n } = useImportDataSink();\n const emitBigConsoleEvent = useBigConsoleAnalytics();\n\n // Reset state when dialog opens\n useEffect(() => {\n if (isOpen) {\n setStep('select');\n setSelectedFile(null);\n setFileContent(null);\n setNameOverride('');\n setKeyOverride('');\n clearState();\n }\n }, [isOpen, clearState]);\n\n // Handle file selection\n const handleFileSelect = useCallback(\n async (e: React.ChangeEvent<HTMLInputElement>) => {\n const file = e.target.files?.[0];\n if (!file) return;\n\n if (!file.name.endsWith('.json')) {\n alert(t('bigconsole.dataSinks.import.selectJsonFile', 'Please select a JSON file'));\n return;\n }\n\n setSelectedFile(file);\n\n const reader = new FileReader();\n reader.onload = async (event) => {\n const content = event.target?.result as string;\n setFileContent(content);\n\n const result = validateImport(content);\n if (result.isValid && result.dataSinkInfo) {\n setStep('preview');\n setNameOverride(result.dataSinkInfo.name || '');\n setKeyOverride(result.dataSinkInfo.key || '');\n } else {\n setStep('preview');\n }\n };\n reader.onerror = () => {\n alert(t('bigconsole.dataSinks.import.readFileError', 'Failed to read file'));\n };\n reader.readAsText(file);\n },\n [validateImport, t]\n );\n\n // Handle file drop\n const handleDrop = useCallback((e: React.DragEvent) => {\n e.preventDefault();\n const file = e.dataTransfer.files?.[0];\n if (file) {\n const dt = new DataTransfer();\n dt.items.add(file);\n if (fileInputRef.current) {\n fileInputRef.current.files = dt.files;\n fileInputRef.current.dispatchEvent(new Event('change', { bubbles: true }));\n }\n }\n }, []);\n\n // Handle import\n const handleImport = useCallback(async () => {\n if (!fileContent) return;\n\n setStep('importing');\n\n const options: ImportDataSinkOptions = {\n name: nameOverride.trim() || undefined,\n key: keyOverride.trim() || undefined,\n };\n\n emitBigConsoleEvent('bigconsole.data_sink.import_clicked', { source: 'import' });\n\n let result: Awaited<ReturnType<typeof importDataSink>>;\n try {\n result = await importDataSink(fileContent, options);\n } catch (err) {\n // Quota refusals (e.g. \"Set up billing\" for an org without a billing\n // account, BOFF-7913) are shown by the shared quota dialog.\n if (reportBigConsoleQuotaError(err)) {\n onClose();\n return;\n }\n setStep('preview');\n return;\n }\n\n if (result?.success && result.dataSink) {\n setStep('complete');\n emitBigConsoleEvent('bigconsole.data_sink.imported', {\n dataSinkId: result.dataSink.id,\n source: 'import',\n originalUpdated: result.originalUpdated ?? false,\n });\n onSuccess?.(result.dataSink.id, result.dataSink.name, result.originalUpdated ?? false);\n } else {\n setStep('preview');\n }\n }, [emitBigConsoleEvent, fileContent, importDataSink, keyOverride, nameOverride, onClose, onSuccess]);\n\n const canClose = !loading && step !== 'importing';\n const dataSinkId = fileContent ? extractDataSinkId(fileContent) : null;\n const currentStepIndex = wizardIndex(step);\n\n return (\n <Dialog open={isOpen} onOpenChange={(open) => !open && canClose && onClose()}>\n <DialogContent className=\"flex max-h-[85vh] flex-col sm:max-w-lg\">\n <DialogHeader>\n <div className=\"flex items-center gap-3\">\n <div className=\"flex size-10 items-center justify-center rounded-card bg-bg-accentSubtle\">\n <Upload className=\"size-5 text-text-link\" />\n </div>\n <DialogTitle>{t('bigconsole.dataSinks.import.title', 'Import DataSink')}</DialogTitle>\n </div>\n <DialogDescription>\n {t(\n 'bigconsole.dataSinks.import.description',\n 'Upload a DataSink export file to restore it into this workspace.'\n )}\n </DialogDescription>\n </DialogHeader>\n\n {/* Step indicator — structural strip so the wizard reads as a sequence. */}\n <ol className=\"flex flex-wrap items-center gap-x-icon-gap gap-y-1 text-xs\" aria-label=\"Import progress\">\n {WIZARD_STEPS.map((wizardStep, index) => (\n <li\n key={wizardStep.id}\n aria-current={index === currentStepIndex ? 'step' : undefined}\n className={`flex items-center gap-icon-gap ${\n index === currentStepIndex ? 'font-semibold text-text-primary' : 'text-text-muted'\n }`}\n >\n <span\n className={`inline-flex size-5 items-center justify-center rounded-pill text-[10px] ${\n index <= currentStepIndex ? 'bg-action-primary-bg text-action-primary-text' : 'bg-bg-sunken'\n }`}\n >\n {index + 1}\n </span>\n {wizardStep.label}\n {index < WIZARD_STEPS.length - 1 && (\n <span aria-hidden=\"true\" className=\"text-text-muted\">\n ›\n </span>\n )}\n </li>\n ))}\n </ol>\n\n <div className=\"min-w-0 flex-1 overflow-y-auto py-4\">\n <SectionStack>\n {/* Step 1: File Selection */}\n {step === 'select' && (\n <Section\n title=\"Choose an export file\"\n description={t(\n 'bigconsole.dataSinks.import.supportedFilesHint',\n 'Only .json files exported from BigConsole are supported'\n )}\n headingLevel={3}\n bodyClassName=\"mt-form-gap\"\n >\n <div\n onDragOver={(e) => e.preventDefault()}\n onDrop={handleDrop}\n className=\"rounded-card border-2 border-dashed border-[var(--color-border-default)] p-8 text-center hover:border-[var(--color-action-primary-bg)] motion-safe:transition-colors\"\n >\n <FileJson className=\"mx-auto mb-4 size-12 text-text-secondary\" />\n <p className=\"mb-2 text-sm\">\n {t('bigconsole.dataSinks.import.dropzoneText', 'Drag and drop a DataSink export file here')}\n </p>\n <p className=\"mb-4 text-xs text-text-secondary\">{t('bigconsole.dataSinks.import.or', 'or')}</p>\n <input ref={fileInputRef} type=\"file\" accept=\".json\" onChange={handleFileSelect} className=\"hidden\" />\n <Button onClick={() => fileInputRef.current?.click()} disabled={loading}>\n {t('bigconsole.dataSinks.import.browseFiles', 'Browse Files')}\n </Button>\n </div>\n </Section>\n )}\n\n {/* Step 2: Preview & Options */}\n {(step === 'preview' || step === 'importing') && validationResult && (\n <>\n {/* What is in the file — supporting detail. */}\n {validationResult.dataSinkInfo && (\n <Section\n emphasis=\"muted\"\n padding=\"compact\"\n headingLevel={3}\n title=\"File contents\"\n description=\"What this export file describes.\"\n bodyClassName=\"space-y-2\"\n >\n <div className=\"flex items-center justify-between gap-icon-gap\">\n <span className=\"text-sm text-text-secondary\">\n {t('bigconsole.dataSinks.import.nameLabel', 'Name')}\n </span>\n <span className=\"min-w-0 truncate text-sm font-medium\">\n {validationResult.dataSinkInfo.name || t('bigconsole.dataSinks.import.unnamed', 'Unnamed')}\n </span>\n </div>\n <div className=\"flex items-center justify-between gap-icon-gap\">\n <span className=\"text-sm text-text-secondary\">\n {t('bigconsole.dataSinks.import.keyLabel', 'Key')}\n </span>\n <span className=\"min-w-0 truncate font-mono text-xs\">{validationResult.dataSinkInfo.key}</span>\n </div>\n <div className=\"flex items-center justify-between gap-icon-gap\">\n <span className=\"text-sm text-text-secondary\">\n {t('bigconsole.dataSinks.import.statusLabel', 'Status')}\n </span>\n <Badge variant=\"secondary\">{validationResult.dataSinkInfo.status}</Badge>\n </div>\n <div className=\"flex items-center justify-between gap-icon-gap\">\n <span className=\"text-sm text-text-secondary\">\n {t('bigconsole.dataSinks.import.setupStateLabel', 'Setup State')}\n </span>\n <Badge variant=\"outline\">{validationResult.dataSinkInfo.setupState}</Badge>\n </div>\n {validationResult.dataSinkInfo.exportedFrom && (\n <div className=\"flex items-center justify-between gap-icon-gap\">\n <span className=\"text-sm text-text-secondary\">\n {t('bigconsole.dataSinks.import.sourceIdLabel', 'Source ID')}\n </span>\n <span className=\"min-w-0 max-w-[180px] truncate font-mono text-xs text-text-secondary\">\n {validationResult.dataSinkInfo.exportedFrom}\n </span>\n </div>\n )}\n <div className=\"flex items-center justify-between gap-icon-gap\">\n <span className=\"text-sm text-text-secondary\">\n {t('bigconsole.dataSinks.import.schemaVersionLabel', 'Schema Version')}\n </span>\n <span className=\"text-xs text-text-secondary\">{validationResult.dataSinkInfo.schemaVersion}</span>\n </div>\n </Section>\n )}\n\n {/* Validation Errors */}\n {validationResult.errors.length > 0 && (\n <Section\n tone=\"danger\"\n padding=\"compact\"\n headingLevel={3}\n title={t('bigconsole.dataSinks.import.errorsHeading', 'Errors ({{count}})', {\n count: validationResult.errors.length,\n })}\n icon={<XCircle className=\"size-4\" />}\n >\n <ul className=\"space-y-1 text-xs text-status-error-text\">\n {validationResult.errors.map((err, idx) => (\n <li key={idx}>\n <span className=\"font-mono\">[{err.code}]</span> {err.message}\n </li>\n ))}\n </ul>\n </Section>\n )}\n\n {/* Import Options */}\n {validationResult.isValid && (\n <Section\n title={t('bigconsole.dataSinks.import.optionsHeading', 'Import Options')}\n description=\"Rename the sink or give it a different key before importing.\"\n headingLevel={3}\n bodyClassName=\"space-y-form-gap\"\n >\n <Section\n tone=\"info\"\n emphasis=\"muted\"\n padding=\"compact\"\n headingLevel={4}\n icon={<Info className=\"size-4\" />}\n title={\n dataSinkId\n ? t(\n 'bigconsole.dataSinks.import.checkExistingHeading',\n 'Import will check for existing DataSink'\n )\n : t('bigconsole.dataSinks.import.newWillBeCreatedHeading', 'A new DataSink will be created')\n }\n >\n <div className=\"text-xs text-[var(--color-status-info-text)]\">\n {dataSinkId ? (\n <>\n <p>\n {t('bigconsole.dataSinks.import.ifOriginalExistsPrefix', 'If the original DataSink (ID:')}{' '}\n <code className=\"rounded-input bg-bg-surface px-1 font-mono\">\n {dataSinkId.slice(0, 8)}...\n </code>\n {t('bigconsole.dataSinks.import.ifOriginalExistsSuffix', ') exists:')}\n </p>\n <ul className=\"mt-1 list-inside list-disc space-y-0.5\">\n <li>\n {t(\n 'bigconsole.dataSinks.import.withChangesUpdate',\n 'With changes → Update the existing DataSink'\n )}\n </li>\n <li>\n {t(\n 'bigconsole.dataSinks.import.noChangesMessage',\n 'No changes → Show \"already exists\" message'\n )}\n </li>\n </ul>\n <p className=\"mt-1\">\n {t(\n 'bigconsole.dataSinks.import.ifNotExistsCreate',\n \"If original doesn't exist → Create a new DataSink\"\n )}\n </p>\n </>\n ) : (\n <p>\n {t(\n 'bigconsole.dataSinks.import.newWillBeCreatedBody',\n 'Import as a new DataSink with the specified configuration.'\n )}\n </p>\n )}\n </div>\n </Section>\n\n <div className=\"space-y-2\">\n <Label htmlFor=\"import-name\">\n {t('bigconsole.dataSinks.import.nameFieldLabel', 'DataSink Name')}\n </Label>\n <Input\n id=\"import-name\"\n type=\"text\"\n value={nameOverride}\n onChange={(e) => setNameOverride(e.target.value)}\n placeholder={\n validationResult.dataSinkInfo?.name ||\n t('bigconsole.dataSinks.import.namePlaceholder', 'DataSink name')\n }\n disabled={step === 'importing'}\n />\n </div>\n\n <div className=\"space-y-2\">\n <Label htmlFor=\"import-key\">\n {t('bigconsole.dataSinks.import.keyFieldLabel', 'DataSink Key')}\n </Label>\n <Input\n id=\"import-key\"\n type=\"text\"\n value={keyOverride}\n onChange={(e) => setKeyOverride(e.target.value)}\n placeholder={\n validationResult.dataSinkInfo?.key ||\n t('bigconsole.dataSinks.import.keyPlaceholder', 'datasink-key')\n }\n disabled={step === 'importing'}\n />\n <p className=\"text-xs text-text-secondary\">\n {t(\n 'bigconsole.dataSinks.import.keyHelp',\n 'Use a unique key to avoid conflicts with existing DataSinks.'\n )}\n </p>\n </div>\n </Section>\n )}\n </>\n )}\n\n {/* Step 3: Complete */}\n {step === 'complete' && importResult?.success && (\n <>\n {/* Already Exists - No Changes */}\n {importResult.alreadyExists && (\n <Section\n tone=\"info\"\n headingLevel={3}\n title={t('bigconsole.dataSinks.import.alreadyExistsTitle', 'DataSink Already Exists')}\n icon={<Info className=\"h-icon-sm w-icon-sm\" />}\n >\n <p className=\"text-sm text-text-secondary\">\n {t(\n 'bigconsole.dataSinks.import.alreadyExistsBody',\n 'DataSink \"{{name}}\" already exists with the same data. No changes were made.',\n { name: importResult.dataSink?.name || importResult.dataSink?.key || '' }\n )}\n </p>\n </Section>\n )}\n\n {/* Updated Existing */}\n {importResult.originalUpdated && !importResult.alreadyExists && (\n <Section\n tone=\"success\"\n headingLevel={3}\n title={t('bigconsole.dataSinks.import.updatedTitle', 'DataSink Updated!')}\n icon={<RefreshCw className=\"h-icon-sm w-icon-sm\" />}\n >\n <p className=\"text-sm text-text-secondary\">\n {t(\n 'bigconsole.dataSinks.import.updatedBody',\n 'DataSink \"{{name}}\" has been updated with the imported data.',\n { name: importResult.dataSink?.name || importResult.dataSink?.key || '' }\n )}\n </p>\n </Section>\n )}\n\n {/* Created New */}\n {!importResult.originalUpdated && !importResult.alreadyExists && (\n <Section\n tone=\"success\"\n headingLevel={3}\n title={t('bigconsole.dataSinks.import.successTitle', 'Import Successful!')}\n icon={<Check className=\"h-icon-sm w-icon-sm\" />}\n >\n <p className=\"text-sm text-text-secondary\">\n {t('bigconsole.dataSinks.import.successBody', 'DataSink \"{{name}}\" has been created.', {\n name: importResult.dataSink?.name || importResult.dataSink?.key || '',\n })}\n </p>\n </Section>\n )}\n </>\n )}\n\n {/* Error */}\n {error && (\n <Section tone=\"danger\" padding=\"compact\" headingLevel={3} title=\"Import failed\">\n <p className=\"text-sm text-status-error-text\">{error.message}</p>\n </Section>\n )}\n\n {importResult && !importResult.success && importResult.errorMessage && (\n <Section tone=\"danger\" padding=\"compact\" headingLevel={3} title=\"Import failed\">\n <p className=\"text-sm text-status-error-text\">{importResult.errorMessage}</p>\n </Section>\n )}\n </SectionStack>\n </div>\n\n <DialogFooter className=\"flex flex-col-reverse gap-control-gap sm:flex-row sm:justify-between\">\n {step === 'preview' ? (\n <Button\n variant=\"ghost\"\n onClick={() => {\n setStep('select');\n setSelectedFile(null);\n setFileContent(null);\n clearState();\n }}\n disabled={loading}\n >\n <ArrowLeft className=\"size-4\" />\n {t('bigconsole.dataSinks.import.back', 'Back')}\n </Button>\n ) : (\n <span aria-hidden=\"true\" className=\"hidden sm:block\" />\n )}\n\n <div className=\"flex flex-wrap items-center gap-2\">\n {step !== 'complete' && (\n <Button variant=\"outline\" onClick={onClose} disabled={!canClose}>\n {t('bigconsole.dataSinks.import.cancel', 'Cancel')}\n </Button>\n )}\n\n {step === 'preview' && validationResult?.isValid && (\n <Button onClick={handleImport} disabled={loading}>\n <Upload className=\"size-4\" />\n {t('bigconsole.dataSinks.import.importButton', 'Import DataSink')}\n </Button>\n )}\n\n {step === 'importing' && (\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <Spinner className=\"size-4\" />\n {t('bigconsole.dataSinks.import.importing', 'Importing...')}\n </div>\n )}\n\n {step === 'complete' && <Button onClick={onClose}>{t('bigconsole.dataSinks.import.done', 'Done')}</Button>}\n </div>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n});\n\nexport default ImportDataSinkDialog;\n"],"mappings":"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"}
@@ -43,8 +43,8 @@ function s() {
43
43
  });
44
44
  }
45
45
  function c({ model: t }) {
46
- let [a, c] = n(0), l = t.pages, u = t.presentation, d = u?.showTitle ? (u.title ?? "").trim() : "", f = u?.showBranding === !0, p = l[Math.min(a, l.length - 1)];
47
- return p ? /* @__PURE__ */ r(e, {
46
+ let [a, c] = n(0), l = t.pages, u = t.presentation, d = u?.showTitle ? (u.title ?? "").trim() : "", f = l[Math.min(a, l.length - 1)];
47
+ return f ? /* @__PURE__ */ r(e, {
48
48
  label: "dashboard",
49
49
  children: /* @__PURE__ */ i("div", {
50
50
  className: "flex h-full w-full flex-col bg-bg-canvas",
@@ -70,11 +70,11 @@ function c({ model: t }) {
70
70
  /* @__PURE__ */ r("div", {
71
71
  className: "min-h-0 flex-1 overflow-auto",
72
72
  children: /* @__PURE__ */ r(o, {
73
- page: p,
73
+ page: f,
74
74
  capabilities: t.capabilities
75
75
  })
76
76
  }),
77
- f && /* @__PURE__ */ r(s, {})
77
+ /* @__PURE__ */ r(s, {})
78
78
  ]
79
79
  })
80
80
  }) : /* @__PURE__ */ i("div", {
@@ -91,7 +91,7 @@ function c({ model: t }) {
91
91
  className: "flex flex-1 items-center justify-center text-sm text-text-secondary",
92
92
  children: "This dashboard has no pages."
93
93
  }),
94
- f && /* @__PURE__ */ r(s, {})
94
+ /* @__PURE__ */ r(s, {})
95
95
  ]
96
96
  });
97
97
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ReadOnlyDashboardRenderer.js","names":[],"sources":["../../../../src/bigconsole/components/embed/ReadOnlyDashboardRenderer.tsx"],"sourcesContent":["/**\n * Read-only embedded dashboard renderer (BOFF-2986).\n *\n * Renders the sanitized render model's pages + widgets on a simple responsive\n * grid. It is STRICTLY view-only: no builder toolbar, edit toggle, sharing,\n * save, palette, inspector or comments — those authenticated affordances do not\n * exist in this component tree at all.\n */\n\nimport { useState } from 'react';\nimport type { EmbedCapabilities, EmbedPageModel, EmbedRenderModel } from './types';\nimport { EmbedWidgetRenderer } from './EmbedWidgetRenderer';\nimport { EmbedErrorBoundary } from './EmbedErrorBoundary';\n\nconst GRID_COLUMNS = 12;\n\ninterface Props {\n readonly model: EmbedRenderModel;\n}\n\nfunction PageGrid({ page, capabilities }: { page: EmbedPageModel; capabilities: EmbedCapabilities }) {\n if (page.widgets.length === 0) {\n return (\n <div className=\"flex h-40 items-center justify-center text-sm text-text-secondary\">This page has no widgets.</div>\n );\n }\n return (\n <div\n className=\"grid w-full gap-4 p-4\"\n style={{ gridTemplateColumns: `repeat(${GRID_COLUMNS}, minmax(0, 1fr))`, gridAutoRows: 'minmax(80px, auto)' }}\n >\n {page.widgets.map((widget) => (\n <div\n key={widget.id}\n className=\"overflow-hidden rounded-lg border border-border-default bg-bg-surface shadow-sm\"\n style={{\n // Clamp both ends: negative/oversized coordinates from malformed\n // model data must never produce an invalid CSS grid line.\n gridColumn: `${Math.min(Math.max(widget.layout.x, 0), GRID_COLUMNS - 1) + 1} / span ${Math.min(\n Math.max(widget.layout.w, 1),\n GRID_COLUMNS\n )}`,\n gridRow: `${Math.max(widget.layout.y, 0) + 1} / span ${Math.max(widget.layout.h, 1)}`,\n }}\n >\n <EmbedWidgetRenderer widget={widget} capabilities={capabilities} />\n </div>\n ))}\n </div>\n );\n}\n\n/** Subtle attribution/upsell footer (BOFF-5735), shown only when the owner opts in. */\nfunction BrandingFooter() {\n return (\n <div className=\"flex shrink-0 items-center justify-end border-t border-border-default bg-bg-surface px-4 py-2\">\n <a\n href=\"https://bigconsole.com\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-xs text-text-tertiary transition-colors hover:text-text-secondary\"\n >\n Powered by <span className=\"font-semibold text-text-secondary\">BigConsole</span>\n </a>\n </div>\n );\n}\n\nexport function ReadOnlyDashboardRenderer({ model }: Props) {\n const [activePageIndex, setActivePageIndex] = useState(0);\n const pages = model.pages;\n const presentation = model.presentation;\n // Only show a header when the owner enabled it AND there's a non-empty title.\n const headerTitle = presentation?.showTitle ? (presentation.title ?? '').trim() : '';\n const showBranding = presentation?.showBranding === true;\n const activePage = pages[Math.min(activePageIndex, pages.length - 1)];\n\n if (!activePage) {\n return (\n <div className=\"flex h-full flex-col bg-bg-canvas\">\n {headerTitle && (\n <header className=\"shrink-0 border-b border-border-default bg-bg-surface px-4 py-3\">\n <h1 className=\"truncate text-base font-semibold text-text-primary\">{headerTitle}</h1>\n </header>\n )}\n <div className=\"flex flex-1 items-center justify-center text-sm text-text-secondary\">\n This dashboard has no pages.\n </div>\n {showBranding && <BrandingFooter />}\n </div>\n );\n }\n\n return (\n <EmbedErrorBoundary label=\"dashboard\">\n <div className=\"flex h-full w-full flex-col bg-bg-canvas\">\n {headerTitle && (\n <header className=\"shrink-0 border-b border-border-default bg-bg-surface px-4 py-3\">\n <h1 className=\"truncate text-base font-semibold text-text-primary\">{headerTitle}</h1>\n </header>\n )}\n {pages.length > 1 && (\n <div role=\"tablist\" className=\"flex gap-1 border-b border-border-default bg-bg-surface px-2\">\n {pages.map((page, i) => (\n <button\n key={page.id}\n role=\"tab\"\n aria-selected={i === activePageIndex}\n onClick={() => setActivePageIndex(i)}\n className={\n i === activePageIndex\n ? '-mb-px border-b-2 border-action-primary-bg px-3 py-2 text-sm font-medium text-text-primary'\n : 'border-b-2 border-transparent px-3 py-2 text-sm text-text-secondary hover:text-text-primary'\n }\n >\n {page.title ?? `Page ${i + 1}`}\n </button>\n ))}\n </div>\n )}\n <div className=\"min-h-0 flex-1 overflow-auto\">\n <PageGrid page={activePage} capabilities={model.capabilities} />\n </div>\n {showBranding && <BrandingFooter />}\n </div>\n </EmbedErrorBoundary>\n );\n}\n"],"mappings":";;;;;AAcA,IAAM,IAAe;AAMrB,SAAS,EAAS,EAAE,SAAM,mBAA2E;AAMnG,QALI,EAAK,QAAQ,WAAW,IAExB,kBAAC,OAAD;EAAK,WAAU;YAAoE;EAA+B,CAAA,GAIpH,kBAAC,OAAD;EACE,WAAU;EACV,OAAO;GAAE,qBAAqB,UAAU,EAAa;GAAoB,cAAc;GAAsB;YAE5G,EAAK,QAAQ,KAAK,MACjB,kBAAC,OAAD;GAEE,WAAU;GACV,OAAO;IAGL,YAAY,GAAG,KAAK,IAAI,KAAK,IAAI,EAAO,OAAO,GAAG,EAAE,EAAE,IAAe,EAAE,GAAG,EAAE,UAAU,KAAK,IACzF,KAAK,IAAI,EAAO,OAAO,GAAG,EAAE,EAC5B,EACD;IACD,SAAS,GAAG,KAAK,IAAI,EAAO,OAAO,GAAG,EAAE,GAAG,EAAE,UAAU,KAAK,IAAI,EAAO,OAAO,GAAG,EAAE;IACpF;aAED,kBAAC,GAAD;IAA6B;IAAsB;IAAgB,CAAA;GAC/D,EAbC,EAAO,GAaR,CACN;EACE,CAAA;;AAKV,SAAS,IAAiB;AACxB,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,KAAD;GACE,MAAK;GACL,QAAO;GACP,KAAI;GACJ,WAAU;aAJZ,CAKC,eACY,kBAAC,QAAD;IAAM,WAAU;cAAoC;IAAiB,CAAA,CAC9E;;EACA,CAAA;;AAIV,SAAgB,EAA0B,EAAE,YAAgB;CAC1D,IAAM,CAAC,GAAiB,KAAsB,EAAS,EAAE,EACnD,IAAQ,EAAM,OACd,IAAe,EAAM,cAErB,IAAc,GAAc,aAAa,EAAa,SAAS,IAAI,MAAM,GAAG,IAC5E,IAAe,GAAc,iBAAiB,IAC9C,IAAa,EAAM,KAAK,IAAI,GAAiB,EAAM,SAAS,EAAE;AAkBpE,QAhBK,IAiBH,kBAAC,GAAD;EAAoB,OAAM;YACxB,kBAAC,OAAD;GAAK,WAAU;aAAf;IACG,KACC,kBAAC,UAAD;KAAQ,WAAU;eAChB,kBAAC,MAAD;MAAI,WAAU;gBAAsD;MAAiB,CAAA;KAC9E,CAAA;IAEV,EAAM,SAAS,KACd,kBAAC,OAAD;KAAK,MAAK;KAAU,WAAU;eAC3B,EAAM,KAAK,GAAM,MAChB,kBAAC,UAAD;MAEE,MAAK;MACL,iBAAe,MAAM;MACrB,eAAe,EAAmB,EAAE;MACpC,WACE,MAAM,IACF,+FACA;gBAGL,EAAK,SAAS,QAAQ,IAAI;MACpB,EAXF,EAAK,GAWH,CACT;KACE,CAAA;IAER,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD;MAAU,MAAM;MAAY,cAAc,EAAM;MAAgB,CAAA;KAC5D,CAAA;IACL,KAAgB,kBAAC,GAAD,EAAkB,CAAA;IAC/B;;EACa,CAAA,GA9CnB,kBAAC,OAAD;EAAK,WAAU;YAAf;GACG,KACC,kBAAC,UAAD;IAAQ,WAAU;cAChB,kBAAC,MAAD;KAAI,WAAU;eAAsD;KAAiB,CAAA;IAC9E,CAAA;GAEX,kBAAC,OAAD;IAAK,WAAU;cAAsE;IAE/E,CAAA;GACL,KAAgB,kBAAC,GAAD,EAAkB,CAAA;GAC/B"}
1
+ {"version":3,"file":"ReadOnlyDashboardRenderer.js","names":[],"sources":["../../../../src/bigconsole/components/embed/ReadOnlyDashboardRenderer.tsx"],"sourcesContent":["/**\n * Read-only embedded dashboard renderer (BOFF-2986).\n *\n * Renders the sanitized render model's pages + widgets on a simple responsive\n * grid. It is STRICTLY view-only: no builder toolbar, edit toggle, sharing,\n * save, palette, inspector or comments — those authenticated affordances do not\n * exist in this component tree at all.\n *\n * Every embed carries the \"Powered by BigConsole\" footer. Burdenoff does no\n * white-labelling, so there is deliberately no model field, policy option or\n * URL parameter that hides it.\n */\n\nimport { useState } from 'react';\nimport type { EmbedCapabilities, EmbedPageModel, EmbedRenderModel } from './types';\nimport { EmbedWidgetRenderer } from './EmbedWidgetRenderer';\nimport { EmbedErrorBoundary } from './EmbedErrorBoundary';\n\nconst GRID_COLUMNS = 12;\n\ninterface Props {\n readonly model: EmbedRenderModel;\n}\n\nfunction PageGrid({ page, capabilities }: { page: EmbedPageModel; capabilities: EmbedCapabilities }) {\n if (page.widgets.length === 0) {\n return (\n <div className=\"flex h-40 items-center justify-center text-sm text-text-secondary\">This page has no widgets.</div>\n );\n }\n return (\n <div\n className=\"grid w-full gap-4 p-4\"\n style={{ gridTemplateColumns: `repeat(${GRID_COLUMNS}, minmax(0, 1fr))`, gridAutoRows: 'minmax(80px, auto)' }}\n >\n {page.widgets.map((widget) => (\n <div\n key={widget.id}\n className=\"overflow-hidden rounded-lg border border-border-default bg-bg-surface shadow-sm\"\n style={{\n // Clamp both ends: negative/oversized coordinates from malformed\n // model data must never produce an invalid CSS grid line.\n gridColumn: `${Math.min(Math.max(widget.layout.x, 0), GRID_COLUMNS - 1) + 1} / span ${Math.min(\n Math.max(widget.layout.w, 1),\n GRID_COLUMNS\n )}`,\n gridRow: `${Math.max(widget.layout.y, 0) + 1} / span ${Math.max(widget.layout.h, 1)}`,\n }}\n >\n <EmbedWidgetRenderer widget={widget} capabilities={capabilities} />\n </div>\n ))}\n </div>\n );\n}\n\n/** \"Powered by BigConsole\" attribution footer, rendered on every embed (never optional). */\nfunction BrandingFooter() {\n return (\n <div className=\"flex shrink-0 items-center justify-end border-t border-border-default bg-bg-surface px-4 py-2\">\n <a\n href=\"https://bigconsole.com\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-xs text-text-tertiary transition-colors hover:text-text-secondary\"\n >\n Powered by <span className=\"font-semibold text-text-secondary\">BigConsole</span>\n </a>\n </div>\n );\n}\n\nexport function ReadOnlyDashboardRenderer({ model }: Props) {\n const [activePageIndex, setActivePageIndex] = useState(0);\n const pages = model.pages;\n const presentation = model.presentation;\n // Only show a header when the owner enabled it AND there's a non-empty title.\n const headerTitle = presentation?.showTitle ? (presentation.title ?? '').trim() : '';\n const activePage = pages[Math.min(activePageIndex, pages.length - 1)];\n\n if (!activePage) {\n return (\n <div className=\"flex h-full flex-col bg-bg-canvas\">\n {headerTitle && (\n <header className=\"shrink-0 border-b border-border-default bg-bg-surface px-4 py-3\">\n <h1 className=\"truncate text-base font-semibold text-text-primary\">{headerTitle}</h1>\n </header>\n )}\n <div className=\"flex flex-1 items-center justify-center text-sm text-text-secondary\">\n This dashboard has no pages.\n </div>\n <BrandingFooter />\n </div>\n );\n }\n\n return (\n <EmbedErrorBoundary label=\"dashboard\">\n <div className=\"flex h-full w-full flex-col bg-bg-canvas\">\n {headerTitle && (\n <header className=\"shrink-0 border-b border-border-default bg-bg-surface px-4 py-3\">\n <h1 className=\"truncate text-base font-semibold text-text-primary\">{headerTitle}</h1>\n </header>\n )}\n {pages.length > 1 && (\n <div role=\"tablist\" className=\"flex gap-1 border-b border-border-default bg-bg-surface px-2\">\n {pages.map((page, i) => (\n <button\n key={page.id}\n role=\"tab\"\n aria-selected={i === activePageIndex}\n onClick={() => setActivePageIndex(i)}\n className={\n i === activePageIndex\n ? '-mb-px border-b-2 border-action-primary-bg px-3 py-2 text-sm font-medium text-text-primary'\n : 'border-b-2 border-transparent px-3 py-2 text-sm text-text-secondary hover:text-text-primary'\n }\n >\n {page.title ?? `Page ${i + 1}`}\n </button>\n ))}\n </div>\n )}\n <div className=\"min-h-0 flex-1 overflow-auto\">\n <PageGrid page={activePage} capabilities={model.capabilities} />\n </div>\n <BrandingFooter />\n </div>\n </EmbedErrorBoundary>\n );\n}\n"],"mappings":";;;;;AAkBA,IAAM,IAAe;AAMrB,SAAS,EAAS,EAAE,SAAM,mBAA2E;AAMnG,QALI,EAAK,QAAQ,WAAW,IAExB,kBAAC,OAAD;EAAK,WAAU;YAAoE;EAA+B,CAAA,GAIpH,kBAAC,OAAD;EACE,WAAU;EACV,OAAO;GAAE,qBAAqB,UAAU,EAAa;GAAoB,cAAc;GAAsB;YAE5G,EAAK,QAAQ,KAAK,MACjB,kBAAC,OAAD;GAEE,WAAU;GACV,OAAO;IAGL,YAAY,GAAG,KAAK,IAAI,KAAK,IAAI,EAAO,OAAO,GAAG,EAAE,EAAE,IAAe,EAAE,GAAG,EAAE,UAAU,KAAK,IACzF,KAAK,IAAI,EAAO,OAAO,GAAG,EAAE,EAC5B,EACD;IACD,SAAS,GAAG,KAAK,IAAI,EAAO,OAAO,GAAG,EAAE,GAAG,EAAE,UAAU,KAAK,IAAI,EAAO,OAAO,GAAG,EAAE;IACpF;aAED,kBAAC,GAAD;IAA6B;IAAsB;IAAgB,CAAA;GAC/D,EAbC,EAAO,GAaR,CACN;EACE,CAAA;;AAKV,SAAS,IAAiB;AACxB,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,KAAD;GACE,MAAK;GACL,QAAO;GACP,KAAI;GACJ,WAAU;aAJZ,CAKC,eACY,kBAAC,QAAD;IAAM,WAAU;cAAoC;IAAiB,CAAA,CAC9E;;EACA,CAAA;;AAIV,SAAgB,EAA0B,EAAE,YAAgB;CAC1D,IAAM,CAAC,GAAiB,KAAsB,EAAS,EAAE,EACnD,IAAQ,EAAM,OACd,IAAe,EAAM,cAErB,IAAc,GAAc,aAAa,EAAa,SAAS,IAAI,MAAM,GAAG,IAC5E,IAAa,EAAM,KAAK,IAAI,GAAiB,EAAM,SAAS,EAAE;AAkBpE,QAhBK,IAiBH,kBAAC,GAAD;EAAoB,OAAM;YACxB,kBAAC,OAAD;GAAK,WAAU;aAAf;IACG,KACC,kBAAC,UAAD;KAAQ,WAAU;eAChB,kBAAC,MAAD;MAAI,WAAU;gBAAsD;MAAiB,CAAA;KAC9E,CAAA;IAEV,EAAM,SAAS,KACd,kBAAC,OAAD;KAAK,MAAK;KAAU,WAAU;eAC3B,EAAM,KAAK,GAAM,MAChB,kBAAC,UAAD;MAEE,MAAK;MACL,iBAAe,MAAM;MACrB,eAAe,EAAmB,EAAE;MACpC,WACE,MAAM,IACF,+FACA;gBAGL,EAAK,SAAS,QAAQ,IAAI;MACpB,EAXF,EAAK,GAWH,CACT;KACE,CAAA;IAER,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD;MAAU,MAAM;MAAY,cAAc,EAAM;MAAgB,CAAA;KAC5D,CAAA;IACN,kBAAC,GAAD,EAAkB,CAAA;IACd;;EACa,CAAA,GA9CnB,kBAAC,OAAD;EAAK,WAAU;YAAf;GACG,KACC,kBAAC,UAAD;IAAQ,WAAU;cAChB,kBAAC,MAAD;KAAI,WAAU;eAAsD;KAAiB,CAAA;IAC9E,CAAA;GAEX,kBAAC,OAAD;IAAK,WAAU;cAAsE;IAE/E,CAAA;GACN,kBAAC,GAAD,EAAkB,CAAA;GACd"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","names":[],"sources":["../../../../src/bigconsole/components/embed/types.ts"],"sourcesContent":["/**\n * Stable, versioned embed render-model contract (BOFF-2986).\n *\n * This is the ONLY shape the read-only embed renderer consumes. It is a\n * sanitized DTO produced by the BigConsole publication builder — it deliberately\n * carries NO authenticated stores, Apollo clients, data-source configs,\n * credentials or internal ids. The renderer never fetches data itself; all\n * values are pre-projected into the model or fetched later through the\n * publication-scoped interaction controller using opaque aliases.\n *\n * Backend (publication builder) and frontend (renderer) MUST agree on\n * `EMBED_RENDER_MODEL_VERSION`. A mismatch fails closed at load time rather than\n * rendering a stale/incompatible view.\n */\n\nimport type { WidgetType } from '../../types';\n\n/** Bump on any breaking change to the DTO shapes below. */\nexport const EMBED_RENDER_MODEL_VERSION = 1 as const;\n\n/** Pure layout box (grid units); no store, no event emission. */\nexport interface EmbedWidgetLayout {\n readonly x: number;\n readonly y: number;\n readonly w: number;\n readonly h: number;\n}\n\n/**\n * A single widget as projected for embedding. `config` and `data` are already\n * sanitized and schema-narrowed per widget type by the publication builder; the\n * renderer validates them again against its per-type schema before rendering.\n */\nexport interface EmbedWidgetModel {\n /** Opaque, publication-scoped widget id (not the internal DB id). */\n readonly id: string;\n readonly type: WidgetType;\n readonly title?: string;\n readonly layout: EmbedWidgetLayout;\n /** Sanitized, type-narrowed presentation config. */\n readonly config: Readonly<Record<string, unknown>>;\n /** Sanitized, pre-projected data payload (or null for interaction-fetched). */\n readonly data: Readonly<Record<string, unknown>> | null;\n}\n\nexport interface EmbedPageModel {\n readonly id: string;\n readonly title?: string;\n readonly widgets: readonly EmbedWidgetModel[];\n}\n\n/**\n * Publication-approved runtime capabilities. Everything defaults OFF; the\n * renderer must never enable an interaction the publication did not grant.\n */\nexport interface EmbedCapabilities {\n readonly filters: boolean;\n readonly drilldown: boolean;\n readonly downloads: boolean;\n /** Side-effect actions (forms/webhooks/workflows) — out of scope for read-only MVP. */\n readonly actions: boolean;\n}\n\n/** Publish-time color mode chosen by the owner (BOFF-5735). */\nexport type EmbedThemeMode = 'light' | 'dark' | 'auto';\n\n/**\n * Owner-chosen, publish-time presentation options (BOFF-5735), attached to the\n * render model (resolved live from the policy). Optional: pre-5735 render models\n * carry no `presentation` block, so the renderer must default gracefully (light,\n * no header, no footer) when absent.\n */\nexport interface EmbedPresentation {\n readonly theme: EmbedThemeMode;\n readonly showTitle: boolean;\n /** Dashboard name for the optional header, or null when hidden/empty. */\n readonly title: string | null;\n readonly showBranding: boolean;\n}\n\nexport interface EmbedRenderModel {\n readonly renderModelVersion: number;\n readonly clientContractVersion: number;\n readonly title?: string;\n readonly pages: readonly EmbedPageModel[];\n readonly capabilities: EmbedCapabilities;\n readonly presentation?: EmbedPresentation;\n /** ISO timestamp the immutable publication was frozen. */\n readonly definitionPublishedAt: string;\n}\n\n/** Result of validating an inbound render model before rendering. */\nexport type EmbedModelValidation =\n { readonly ok: true; readonly model: EmbedRenderModel } | { readonly ok: false; readonly reason: string };\n\n/**\n * Shape every embed widget renderer component receives. It gets the sanitized\n * model plus a controller for publication-scoped interactions — never raw hooks.\n */\nexport interface EmbedWidgetRenderProps {\n readonly widget: EmbedWidgetModel;\n readonly capabilities: EmbedCapabilities;\n}\n"],"mappings":";AAkBA,IAAa,IAA6B"}
1
+ {"version":3,"file":"types.js","names":[],"sources":["../../../../src/bigconsole/components/embed/types.ts"],"sourcesContent":["/**\n * Stable, versioned embed render-model contract (BOFF-2986).\n *\n * This is the ONLY shape the read-only embed renderer consumes. It is a\n * sanitized DTO produced by the BigConsole publication builder — it deliberately\n * carries NO authenticated stores, Apollo clients, data-source configs,\n * credentials or internal ids. The renderer never fetches data itself; all\n * values are pre-projected into the model or fetched later through the\n * publication-scoped interaction controller using opaque aliases.\n *\n * Backend (publication builder) and frontend (renderer) MUST agree on\n * `EMBED_RENDER_MODEL_VERSION`. A mismatch fails closed at load time rather than\n * rendering a stale/incompatible view.\n */\n\nimport type { WidgetType } from '../../types';\n\n/** Bump on any breaking change to the DTO shapes below. */\nexport const EMBED_RENDER_MODEL_VERSION = 1 as const;\n\n/** Pure layout box (grid units); no store, no event emission. */\nexport interface EmbedWidgetLayout {\n readonly x: number;\n readonly y: number;\n readonly w: number;\n readonly h: number;\n}\n\n/**\n * A single widget as projected for embedding. `config` and `data` are already\n * sanitized and schema-narrowed per widget type by the publication builder; the\n * renderer validates them again against its per-type schema before rendering.\n */\nexport interface EmbedWidgetModel {\n /** Opaque, publication-scoped widget id (not the internal DB id). */\n readonly id: string;\n readonly type: WidgetType;\n readonly title?: string;\n readonly layout: EmbedWidgetLayout;\n /** Sanitized, type-narrowed presentation config. */\n readonly config: Readonly<Record<string, unknown>>;\n /** Sanitized, pre-projected data payload (or null for interaction-fetched). */\n readonly data: Readonly<Record<string, unknown>> | null;\n}\n\nexport interface EmbedPageModel {\n readonly id: string;\n readonly title?: string;\n readonly widgets: readonly EmbedWidgetModel[];\n}\n\n/**\n * Publication-approved runtime capabilities. Everything defaults OFF; the\n * renderer must never enable an interaction the publication did not grant.\n */\nexport interface EmbedCapabilities {\n readonly filters: boolean;\n readonly drilldown: boolean;\n readonly downloads: boolean;\n /** Side-effect actions (forms/webhooks/workflows) — out of scope for read-only MVP. */\n readonly actions: boolean;\n}\n\n/** Publish-time color mode chosen by the owner (BOFF-5735). */\nexport type EmbedThemeMode = 'light' | 'dark' | 'auto';\n\n/**\n * Owner-chosen, publish-time presentation options (BOFF-5735), attached to the\n * render model (resolved live from the policy). Optional: pre-5735 render models\n * carry no `presentation` block, so the renderer must default gracefully (light,\n * no header) when absent.\n *\n * Branding is NOT a presentation option: the \"Powered by BigConsole\" footer is\n * rendered on every embed, with or without this block. Burdenoff does no\n * white-labelling, so never add a field here that hides or replaces it.\n */\nexport interface EmbedPresentation {\n readonly theme: EmbedThemeMode;\n readonly showTitle: boolean;\n /** Dashboard name for the optional header, or null when hidden/empty. */\n readonly title: string | null;\n}\n\nexport interface EmbedRenderModel {\n readonly renderModelVersion: number;\n readonly clientContractVersion: number;\n readonly title?: string;\n readonly pages: readonly EmbedPageModel[];\n readonly capabilities: EmbedCapabilities;\n readonly presentation?: EmbedPresentation;\n /** ISO timestamp the immutable publication was frozen. */\n readonly definitionPublishedAt: string;\n}\n\n/** Result of validating an inbound render model before rendering. */\nexport type EmbedModelValidation =\n { readonly ok: true; readonly model: EmbedRenderModel } | { readonly ok: false; readonly reason: string };\n\n/**\n * Shape every embed widget renderer component receives. It gets the sanitized\n * model plus a controller for publication-scoped interactions — never raw hooks.\n */\nexport interface EmbedWidgetRenderProps {\n readonly widget: EmbedWidgetModel;\n readonly capabilities: EmbedCapabilities;\n}\n"],"mappings":";AAkBA,IAAa,IAA6B"}
@@ -6,11 +6,11 @@ import { DOCK_CLEARANCE as i, PreviewMeta as a, STEP_LABEL as o, formatAgo as s,
6
6
  import { ArrowUpRight as l, Braces as u, Database as d, LayoutDashboard as f, Loader2 as p, Sparkles as m, X as h } from "lucide-react";
7
7
  import { Button as g } from "@burdenoff/fe-libs/ui";
8
8
  import { jsx as _, jsxs as v } from "react/jsx-runtime";
9
- import { AssistantEntityPreview as y } from "@burdenoff/fe-libs/shared/assistant";
10
- import { useI18n as b } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
9
+ import { useI18n as y } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
10
+ import { AssistantEntityPreview as b } from "@burdenoff/fe-libs/shared/assistant";
11
11
  //#region src/bigconsole/components/preview/AiPreviewMiniPanel.tsx
12
12
  function x({ onNavigate: x }) {
13
- let { t: S } = b(), C = e(), w = n((e) => e.isRunning), T = n((e) => e.previewOpen), E = n((e) => e.hasRun), D = n((e) => e.richMounted), O = n((e) => e.steps), k = n((e) => e.entityIds), A = n((e) => e.caption), j = n((e) => e.error), M = n((e) => e.updatedAt), N = n((e) => e.openPreview), P = n((e) => e.closePreview), F = (e) => {
13
+ let { t: S } = y(), C = e(), w = n((e) => e.isRunning), T = n((e) => e.previewOpen), E = n((e) => e.hasRun), D = n((e) => e.richMounted), O = n((e) => e.steps), k = n((e) => e.entityIds), A = n((e) => e.caption), j = n((e) => e.error), M = n((e) => e.updatedAt), N = n((e) => e.openPreview), P = n((e) => e.closePreview), F = (e) => {
14
14
  x ? x(e) : typeof window < "u" && window.location.assign(e);
15
15
  };
16
16
  if (!E || D || C) return null;
@@ -71,7 +71,7 @@ function x({ onNavigate: x }) {
71
71
  children: [/* @__PURE__ */ _("h2", {
72
72
  className: "sr-only",
73
73
  children: S("bigconsole.widget.aiPreviewMini.title", "AI Preview")
74
- }), /* @__PURE__ */ _(y, {
74
+ }), /* @__PURE__ */ _(b, {
75
75
  title: S("bigconsole.widget.aiPreviewMini.title", "AI Preview"),
76
76
  steps: L,
77
77
  caption: w ? A || S("bigconsole.widget.aiPreviewMini.working", "Working…") : S("bigconsole.widget.aiPreviewMini.done", "Done"),
@@ -10,8 +10,8 @@ import { useEffect as m, useRef as h, useState as g } from "react";
10
10
  import { ArrowUpRight as _, Loader2 as v, Sparkles as y, X as b } from "lucide-react";
11
11
  import { Button as x } from "@burdenoff/fe-libs/ui";
12
12
  import { jsx as S, jsxs as C } from "react/jsx-runtime";
13
- import { AssistantEntityPreview as w } from "@burdenoff/fe-libs/shared/assistant";
14
- import { useI18n as T } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
13
+ import { useI18n as w } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
14
+ import { AssistantEntityPreview as T } from "@burdenoff/fe-libs/shared/assistant";
15
15
  //#region src/bigconsole/components/preview/AiPreviewPanel.tsx
16
16
  function E({ children: e }) {
17
17
  return /* @__PURE__ */ S("div", {
@@ -21,7 +21,7 @@ function E({ children: e }) {
21
21
  });
22
22
  }
23
23
  function D({ onOpen: e }) {
24
- let { t } = T();
24
+ let { t } = w();
25
25
  return /* @__PURE__ */ C(x, {
26
26
  size: "sm",
27
27
  variant: "outline",
@@ -31,7 +31,7 @@ function D({ onOpen: e }) {
31
31
  });
32
32
  }
33
33
  function O() {
34
- let { t: _ } = T(), O = t(), k = e(), A = r((e) => e.isRunning), j = r((e) => e.previewOpen), M = r((e) => e.hasRun), N = r((e) => e.steps), P = r((e) => e.entityIds), F = r((e) => e.caption), I = r((e) => e.error), L = r((e) => e.updatedAt), R = r((e) => e.openPreview), z = r((e) => e.closePreview), B = r((e) => e.setRichMounted);
34
+ let { t: _ } = w(), O = t(), k = e(), A = r((e) => e.isRunning), j = r((e) => e.previewOpen), M = r((e) => e.hasRun), N = r((e) => e.steps), P = r((e) => e.entityIds), F = r((e) => e.caption), I = r((e) => e.error), L = r((e) => e.updatedAt), R = r((e) => e.openPreview), z = r((e) => e.closePreview), B = r((e) => e.setRichMounted);
35
35
  m(() => (B(!0), () => {
36
36
  B(!1);
37
37
  }), [B]);
@@ -131,7 +131,7 @@ function O() {
131
131
  children: [/* @__PURE__ */ S("h2", {
132
132
  className: "sr-only",
133
133
  children: _("bigconsole.widget.aiPreview.title", "AI Preview")
134
- }), /* @__PURE__ */ S(w, {
134
+ }), /* @__PURE__ */ S(T, {
135
135
  title: _("bigconsole.widget.aiPreview.title", "AI Preview"),
136
136
  runKey: String(V),
137
137
  steps: G,
@@ -50,8 +50,7 @@ function p() {
50
50
  allowedParentOrigins: r,
51
51
  ...a ? {
52
52
  presentationTheme: u[a.theme],
53
- showDashboardTitle: a.showTitle,
54
- showBrandingFooter: a.showBranding
53
+ showDashboardTitle: a.showTitle
55
54
  } : {}
56
55
  } }
57
56
  })).data?.createDashboardEmbedPolicy;
@@ -81,8 +80,7 @@ function p() {
81
80
  variables: { input: {
82
81
  policyId: e,
83
82
  presentationTheme: u[t.theme],
84
- showDashboardTitle: t.showTitle,
85
- showBrandingFooter: t.showBranding
83
+ showDashboardTitle: t.showTitle
86
84
  } }
87
85
  })).data?.updateDashboardEmbedPolicy ?? null;
88
86
  return n && d(n), n;
@@ -1 +1 @@
1
- {"version":3,"file":"useDashboardEmbedPolicy.js","names":[],"sources":["../../../src/bigconsole/hooks/useDashboardEmbedPolicy.ts"],"sourcesContent":["/**\n * useDashboardEmbedPolicy Hook\n *\n * Owner-facing operations for publishing a dashboard to the web (anonymous-link\n * embed). Mirrors {@link useDashboardSharing}. All mutations are gateway-enforced\n * by `@rbac(manage_embed)` (owner-only). Publishing serves the dashboard's LIVE\n * data anonymously — the caller confirms content-safety before enabling.\n */\n\nimport { useCallback, useState } from 'react';\nimport { useApolloClient } from '@apollo/client/react';\nimport {\n ListDashboardEmbedPoliciesDocument,\n CreateDashboardEmbedPolicyDocument,\n PublishDashboardEmbedPolicyDocument,\n UpdateDashboardEmbedPolicyDocument,\n RotateDashboardEmbedPolicyDocument,\n RevokeDashboardEmbedPolicyDocument,\n} from '../../generated/wspace-operations';\nimport { DashboardEmbedTheme } from '../../generated/wspace-types';\nimport type { EmbedThemeMode } from '../components/embed/types';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type EmbedPolicyStatus = 'DRAFT' | 'ACTIVE' | 'NEEDS_REVIEW' | 'BROKEN' | 'REVOKED' | 'EXPIRED';\n\n/**\n * Owner-facing color mode (BOFF-5735); maps to the backend {@link DashboardEmbedTheme}\n * enum. Same shape as the render-model's {@link EmbedThemeMode} — aliased to avoid a\n * duplicate union.\n */\nexport type EmbedThemeChoice = EmbedThemeMode;\n\n/** Publish-time presentation options the owner controls in the Share dialog. */\nexport interface EmbedPresentationChoice {\n theme: EmbedThemeChoice;\n showTitle: boolean;\n showBranding: boolean;\n}\n\nconst THEME_TO_ENUM: Record<EmbedThemeChoice, DashboardEmbedTheme> = {\n light: DashboardEmbedTheme.Light,\n dark: DashboardEmbedTheme.Dark,\n auto: DashboardEmbedTheme.Auto,\n};\n\n/** Map the backend enum back to the lowercase owner-facing choice (defaults to light). */\nexport function themeChoiceFromPolicy(value: string | null | undefined): EmbedThemeChoice {\n switch (value) {\n case DashboardEmbedTheme.Dark:\n return 'dark';\n case DashboardEmbedTheme.Auto:\n return 'auto';\n default:\n return 'light';\n }\n}\n\nexport interface DashboardEmbedPolicy {\n id: string;\n dashboardId: string;\n status: EmbedPolicyStatus;\n accessMode: string;\n version: number;\n allowedParentOrigins: string[];\n expiresAt?: string | null;\n sessionTtlSeconds: number;\n contentReviewState: string;\n presentationTheme: DashboardEmbedTheme;\n showDashboardTitle: boolean;\n showBrandingFooter: boolean;\n activePublicationId?: string | null;\n locatorPublicId?: string | null;\n fingerprint?: string | null;\n createdAt: string;\n updatedAt: string;\n activatedAt?: string | null;\n revokedAt?: string | null;\n}\n\n/** Returned once on create / rotate — carries the secret embed URL + snippet. */\nexport interface EmbedPolicySecret {\n embedUrl: string;\n iframeSnippet: string;\n fingerprint: string;\n policy: DashboardEmbedPolicy;\n}\n\nexport interface UseDashboardEmbedPolicyResult {\n /** The dashboard's active (or most recent) embed policy, if any. */\n policy: DashboardEmbedPolicy | null;\n /** The one-time secret from the last create/rotate (embed URL + iframe snippet). */\n secret: EmbedPolicySecret | null;\n loading: boolean;\n error: Error | null;\n\n fetchPolicy: (dashboardId: string) => Promise<DashboardEmbedPolicy | null>;\n /** Create (if needed) + publish → returns the live embed secret. */\n publishToWeb: (\n dashboardId: string,\n allowedParentOrigins: string[],\n contentSafetyAttested: boolean,\n presentation?: EmbedPresentationChoice\n ) => Promise<EmbedPolicySecret | null>;\n /**\n * Persist the presentation options of an already-published policy. They are\n * read live by the embed renderer, so the change applies on the next render\n * with no re-publish — the embed link/key are untouched.\n */\n applyPresentation: (policyId: string, presentation: EmbedPresentationChoice) => Promise<DashboardEmbedPolicy | null>;\n setAllowedOrigins: (policyId: string, allowedParentOrigins: string[]) => Promise<DashboardEmbedPolicy | null>;\n rotateKey: (policyId: string) => Promise<EmbedPolicySecret | null>;\n /** Unpublish: revoke the policy (embed stops serving immediately). */\n unpublish: (policyId: string) => Promise<boolean>;\n}\n\n// ============================================================================\n// Hook\n// ============================================================================\n\n/** Pick the live-serving policy (ACTIVE) else the most recently updated one. */\nfunction pickCurrent(policies: DashboardEmbedPolicy[]): DashboardEmbedPolicy | null {\n if (policies.length === 0) return null;\n const active = policies.find((p) => p.status === 'ACTIVE');\n if (active) return active;\n return [...policies].sort((a, b) => Date.parse(b.updatedAt) - Date.parse(a.updatedAt))[0] ?? null;\n}\n\nexport function useDashboardEmbedPolicy(): UseDashboardEmbedPolicyResult {\n const [policy, setPolicy] = useState<DashboardEmbedPolicy | null>(null);\n const [secret, setSecret] = useState<EmbedPolicySecret | null>(null);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const apolloClient = useApolloClient();\n\n const fetchPolicy = useCallback(\n async (dashboardId: string): Promise<DashboardEmbedPolicy | null> => {\n try {\n setLoading(true);\n setError(null);\n const result = await apolloClient.query<{\n listDashboardEmbedPolicies?: { nodes?: DashboardEmbedPolicy[] };\n }>({\n query: ListDashboardEmbedPoliciesDocument,\n variables: { dashboardId },\n fetchPolicy: 'network-only',\n });\n const current = pickCurrent(result.data?.listDashboardEmbedPolicies?.nodes ?? []);\n setPolicy(current);\n return current;\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to load embed policy'));\n return null;\n } finally {\n setLoading(false);\n }\n },\n [apolloClient]\n );\n\n const publishToWeb = useCallback(\n async (\n dashboardId: string,\n allowedParentOrigins: string[],\n contentSafetyAttested: boolean,\n presentation?: EmbedPresentationChoice\n ): Promise<EmbedPolicySecret | null> => {\n try {\n setLoading(true);\n setError(null);\n\n // Create a DRAFT policy + anonymous-link locator (returns the secret once).\n // Presentation options (BOFF-5735) are captured here so the very first\n // render reflects them; they're read live thereafter.\n const created = await apolloClient.mutate<{ createDashboardEmbedPolicy?: EmbedPolicySecret }>({\n mutation: CreateDashboardEmbedPolicyDocument,\n variables: {\n input: {\n dashboardId,\n allowedParentOrigins,\n ...(presentation\n ? {\n presentationTheme: THEME_TO_ENUM[presentation.theme],\n showDashboardTitle: presentation.showTitle,\n showBrandingFooter: presentation.showBranding,\n }\n : {}),\n },\n },\n });\n const createdSecret = created.data?.createDashboardEmbedPolicy;\n if (!createdSecret) return null;\n\n // Activate it for live embedding (owner attests content-safety).\n const published = await apolloClient.mutate<{ publishDashboardEmbedPolicy?: DashboardEmbedPolicy }>({\n mutation: PublishDashboardEmbedPolicyDocument,\n variables: { policyId: createdSecret.policy.id, contentSafetyAttested },\n });\n const activePolicy = published.data?.publishDashboardEmbedPolicy ?? createdSecret.policy;\n\n const result: EmbedPolicySecret = { ...createdSecret, policy: activePolicy };\n setSecret(result);\n setPolicy(activePolicy);\n return result;\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to publish to web'));\n return null;\n } finally {\n setLoading(false);\n }\n },\n [apolloClient]\n );\n\n const applyPresentation = useCallback(\n async (policyId: string, presentation: EmbedPresentationChoice): Promise<DashboardEmbedPolicy | null> => {\n try {\n setLoading(true);\n setError(null);\n // Presentation is read LIVE at view time by the embed renderer, so simply\n // persisting the choices on the policy is enough — no re-publish, and the\n // embed link/key are untouched. Live embeds pick up the change on their\n // next render (bounded by the service's short render-cache window).\n const result = await apolloClient.mutate<{ updateDashboardEmbedPolicy?: DashboardEmbedPolicy }>({\n mutation: UpdateDashboardEmbedPolicyDocument,\n variables: {\n input: {\n policyId,\n presentationTheme: THEME_TO_ENUM[presentation.theme],\n showDashboardTitle: presentation.showTitle,\n showBrandingFooter: presentation.showBranding,\n },\n },\n });\n const updated = result.data?.updateDashboardEmbedPolicy ?? null;\n if (updated) setPolicy(updated);\n return updated;\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to update presentation'));\n return null;\n } finally {\n setLoading(false);\n }\n },\n [apolloClient]\n );\n\n const setAllowedOrigins = useCallback(\n async (policyId: string, allowedParentOrigins: string[]): Promise<DashboardEmbedPolicy | null> => {\n try {\n setLoading(true);\n setError(null);\n const result = await apolloClient.mutate<{ updateDashboardEmbedPolicy?: DashboardEmbedPolicy }>({\n mutation: UpdateDashboardEmbedPolicyDocument,\n variables: { input: { policyId, allowedParentOrigins } },\n });\n const updated = result.data?.updateDashboardEmbedPolicy ?? null;\n if (updated) setPolicy(updated);\n return updated;\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to update allowed domains'));\n return null;\n } finally {\n setLoading(false);\n }\n },\n [apolloClient]\n );\n\n const rotateKey = useCallback(\n async (policyId: string): Promise<EmbedPolicySecret | null> => {\n try {\n setLoading(true);\n setError(null);\n const result = await apolloClient.mutate<{ rotateDashboardEmbedPolicy?: EmbedPolicySecret }>({\n mutation: RotateDashboardEmbedPolicyDocument,\n variables: { policyId },\n });\n const rotated = result.data?.rotateDashboardEmbedPolicy ?? null;\n if (rotated) {\n setSecret(rotated);\n setPolicy(rotated.policy);\n }\n return rotated;\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to rotate embed link'));\n return null;\n } finally {\n setLoading(false);\n }\n },\n [apolloClient]\n );\n\n const unpublish = useCallback(\n async (policyId: string): Promise<boolean> => {\n try {\n setLoading(true);\n setError(null);\n const result = await apolloClient.mutate<{ revokeDashboardEmbedPolicy?: DashboardEmbedPolicy }>({\n mutation: RevokeDashboardEmbedPolicyDocument,\n variables: { policyId },\n });\n const revoked = result.data?.revokeDashboardEmbedPolicy ?? null;\n if (revoked) {\n setPolicy(revoked);\n setSecret(null);\n return true;\n }\n return false;\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to unpublish'));\n return false;\n } finally {\n setLoading(false);\n }\n },\n [apolloClient]\n );\n\n return {\n policy,\n secret,\n loading,\n error,\n fetchPolicy,\n publishToWeb,\n applyPresentation,\n setAllowedOrigins,\n rotateKey,\n unpublish,\n };\n}\n\nexport default useDashboardEmbedPolicy;\n"],"mappings":"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"}
1
+ {"version":3,"file":"useDashboardEmbedPolicy.js","names":[],"sources":["../../../src/bigconsole/hooks/useDashboardEmbedPolicy.ts"],"sourcesContent":["/**\n * useDashboardEmbedPolicy Hook\n *\n * Owner-facing operations for publishing a dashboard to the web (anonymous-link\n * embed). Mirrors {@link useDashboardSharing}. All mutations are gateway-enforced\n * by `@rbac(manage_embed)` (owner-only). Publishing serves the dashboard's LIVE\n * data anonymously — the caller confirms content-safety before enabling.\n */\n\nimport { useCallback, useState } from 'react';\nimport { useApolloClient } from '@apollo/client/react';\nimport {\n ListDashboardEmbedPoliciesDocument,\n CreateDashboardEmbedPolicyDocument,\n PublishDashboardEmbedPolicyDocument,\n UpdateDashboardEmbedPolicyDocument,\n RotateDashboardEmbedPolicyDocument,\n RevokeDashboardEmbedPolicyDocument,\n} from '../../generated/wspace-operations';\nimport { DashboardEmbedTheme } from '../../generated/wspace-types';\nimport type { EmbedThemeMode } from '../components/embed/types';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type EmbedPolicyStatus = 'DRAFT' | 'ACTIVE' | 'NEEDS_REVIEW' | 'BROKEN' | 'REVOKED' | 'EXPIRED';\n\n/**\n * Owner-facing color mode (BOFF-5735); maps to the backend {@link DashboardEmbedTheme}\n * enum. Same shape as the render-model's {@link EmbedThemeMode} — aliased to avoid a\n * duplicate union.\n */\nexport type EmbedThemeChoice = EmbedThemeMode;\n\n/**\n * Publish-time presentation options the owner controls in the Share dialog.\n * Branding is deliberately absent: every embed carries the \"Powered by\n * BigConsole\" footer (Burdenoff does no white-labelling).\n */\nexport interface EmbedPresentationChoice {\n theme: EmbedThemeChoice;\n showTitle: boolean;\n}\n\nconst THEME_TO_ENUM: Record<EmbedThemeChoice, DashboardEmbedTheme> = {\n light: DashboardEmbedTheme.Light,\n dark: DashboardEmbedTheme.Dark,\n auto: DashboardEmbedTheme.Auto,\n};\n\n/** Map the backend enum back to the lowercase owner-facing choice (defaults to light). */\nexport function themeChoiceFromPolicy(value: string | null | undefined): EmbedThemeChoice {\n switch (value) {\n case DashboardEmbedTheme.Dark:\n return 'dark';\n case DashboardEmbedTheme.Auto:\n return 'auto';\n default:\n return 'light';\n }\n}\n\nexport interface DashboardEmbedPolicy {\n id: string;\n dashboardId: string;\n status: EmbedPolicyStatus;\n accessMode: string;\n version: number;\n allowedParentOrigins: string[];\n expiresAt?: string | null;\n sessionTtlSeconds: number;\n contentReviewState: string;\n presentationTheme: DashboardEmbedTheme;\n showDashboardTitle: boolean;\n activePublicationId?: string | null;\n locatorPublicId?: string | null;\n fingerprint?: string | null;\n createdAt: string;\n updatedAt: string;\n activatedAt?: string | null;\n revokedAt?: string | null;\n}\n\n/** Returned once on create / rotate — carries the secret embed URL + snippet. */\nexport interface EmbedPolicySecret {\n embedUrl: string;\n iframeSnippet: string;\n fingerprint: string;\n policy: DashboardEmbedPolicy;\n}\n\nexport interface UseDashboardEmbedPolicyResult {\n /** The dashboard's active (or most recent) embed policy, if any. */\n policy: DashboardEmbedPolicy | null;\n /** The one-time secret from the last create/rotate (embed URL + iframe snippet). */\n secret: EmbedPolicySecret | null;\n loading: boolean;\n error: Error | null;\n\n fetchPolicy: (dashboardId: string) => Promise<DashboardEmbedPolicy | null>;\n /** Create (if needed) + publish → returns the live embed secret. */\n publishToWeb: (\n dashboardId: string,\n allowedParentOrigins: string[],\n contentSafetyAttested: boolean,\n presentation?: EmbedPresentationChoice\n ) => Promise<EmbedPolicySecret | null>;\n /**\n * Persist the presentation options of an already-published policy. They are\n * read live by the embed renderer, so the change applies on the next render\n * with no re-publish — the embed link/key are untouched.\n */\n applyPresentation: (policyId: string, presentation: EmbedPresentationChoice) => Promise<DashboardEmbedPolicy | null>;\n setAllowedOrigins: (policyId: string, allowedParentOrigins: string[]) => Promise<DashboardEmbedPolicy | null>;\n rotateKey: (policyId: string) => Promise<EmbedPolicySecret | null>;\n /** Unpublish: revoke the policy (embed stops serving immediately). */\n unpublish: (policyId: string) => Promise<boolean>;\n}\n\n// ============================================================================\n// Hook\n// ============================================================================\n\n/** Pick the live-serving policy (ACTIVE) else the most recently updated one. */\nfunction pickCurrent(policies: DashboardEmbedPolicy[]): DashboardEmbedPolicy | null {\n if (policies.length === 0) return null;\n const active = policies.find((p) => p.status === 'ACTIVE');\n if (active) return active;\n return [...policies].sort((a, b) => Date.parse(b.updatedAt) - Date.parse(a.updatedAt))[0] ?? null;\n}\n\nexport function useDashboardEmbedPolicy(): UseDashboardEmbedPolicyResult {\n const [policy, setPolicy] = useState<DashboardEmbedPolicy | null>(null);\n const [secret, setSecret] = useState<EmbedPolicySecret | null>(null);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const apolloClient = useApolloClient();\n\n const fetchPolicy = useCallback(\n async (dashboardId: string): Promise<DashboardEmbedPolicy | null> => {\n try {\n setLoading(true);\n setError(null);\n const result = await apolloClient.query<{\n listDashboardEmbedPolicies?: { nodes?: DashboardEmbedPolicy[] };\n }>({\n query: ListDashboardEmbedPoliciesDocument,\n variables: { dashboardId },\n fetchPolicy: 'network-only',\n });\n const current = pickCurrent(result.data?.listDashboardEmbedPolicies?.nodes ?? []);\n setPolicy(current);\n return current;\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to load embed policy'));\n return null;\n } finally {\n setLoading(false);\n }\n },\n [apolloClient]\n );\n\n const publishToWeb = useCallback(\n async (\n dashboardId: string,\n allowedParentOrigins: string[],\n contentSafetyAttested: boolean,\n presentation?: EmbedPresentationChoice\n ): Promise<EmbedPolicySecret | null> => {\n try {\n setLoading(true);\n setError(null);\n\n // Create a DRAFT policy + anonymous-link locator (returns the secret once).\n // Presentation options (BOFF-5735) are captured here so the very first\n // render reflects them; they're read live thereafter.\n const created = await apolloClient.mutate<{ createDashboardEmbedPolicy?: EmbedPolicySecret }>({\n mutation: CreateDashboardEmbedPolicyDocument,\n variables: {\n input: {\n dashboardId,\n allowedParentOrigins,\n ...(presentation\n ? {\n presentationTheme: THEME_TO_ENUM[presentation.theme],\n showDashboardTitle: presentation.showTitle,\n }\n : {}),\n },\n },\n });\n const createdSecret = created.data?.createDashboardEmbedPolicy;\n if (!createdSecret) return null;\n\n // Activate it for live embedding (owner attests content-safety).\n const published = await apolloClient.mutate<{ publishDashboardEmbedPolicy?: DashboardEmbedPolicy }>({\n mutation: PublishDashboardEmbedPolicyDocument,\n variables: { policyId: createdSecret.policy.id, contentSafetyAttested },\n });\n const activePolicy = published.data?.publishDashboardEmbedPolicy ?? createdSecret.policy;\n\n const result: EmbedPolicySecret = { ...createdSecret, policy: activePolicy };\n setSecret(result);\n setPolicy(activePolicy);\n return result;\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to publish to web'));\n return null;\n } finally {\n setLoading(false);\n }\n },\n [apolloClient]\n );\n\n const applyPresentation = useCallback(\n async (policyId: string, presentation: EmbedPresentationChoice): Promise<DashboardEmbedPolicy | null> => {\n try {\n setLoading(true);\n setError(null);\n // Presentation is read LIVE at view time by the embed renderer, so simply\n // persisting the choices on the policy is enough — no re-publish, and the\n // embed link/key are untouched. Live embeds pick up the change on their\n // next render (bounded by the service's short render-cache window).\n const result = await apolloClient.mutate<{ updateDashboardEmbedPolicy?: DashboardEmbedPolicy }>({\n mutation: UpdateDashboardEmbedPolicyDocument,\n variables: {\n input: {\n policyId,\n presentationTheme: THEME_TO_ENUM[presentation.theme],\n showDashboardTitle: presentation.showTitle,\n },\n },\n });\n const updated = result.data?.updateDashboardEmbedPolicy ?? null;\n if (updated) setPolicy(updated);\n return updated;\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to update presentation'));\n return null;\n } finally {\n setLoading(false);\n }\n },\n [apolloClient]\n );\n\n const setAllowedOrigins = useCallback(\n async (policyId: string, allowedParentOrigins: string[]): Promise<DashboardEmbedPolicy | null> => {\n try {\n setLoading(true);\n setError(null);\n const result = await apolloClient.mutate<{ updateDashboardEmbedPolicy?: DashboardEmbedPolicy }>({\n mutation: UpdateDashboardEmbedPolicyDocument,\n variables: { input: { policyId, allowedParentOrigins } },\n });\n const updated = result.data?.updateDashboardEmbedPolicy ?? null;\n if (updated) setPolicy(updated);\n return updated;\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to update allowed domains'));\n return null;\n } finally {\n setLoading(false);\n }\n },\n [apolloClient]\n );\n\n const rotateKey = useCallback(\n async (policyId: string): Promise<EmbedPolicySecret | null> => {\n try {\n setLoading(true);\n setError(null);\n const result = await apolloClient.mutate<{ rotateDashboardEmbedPolicy?: EmbedPolicySecret }>({\n mutation: RotateDashboardEmbedPolicyDocument,\n variables: { policyId },\n });\n const rotated = result.data?.rotateDashboardEmbedPolicy ?? null;\n if (rotated) {\n setSecret(rotated);\n setPolicy(rotated.policy);\n }\n return rotated;\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to rotate embed link'));\n return null;\n } finally {\n setLoading(false);\n }\n },\n [apolloClient]\n );\n\n const unpublish = useCallback(\n async (policyId: string): Promise<boolean> => {\n try {\n setLoading(true);\n setError(null);\n const result = await apolloClient.mutate<{ revokeDashboardEmbedPolicy?: DashboardEmbedPolicy }>({\n mutation: RevokeDashboardEmbedPolicyDocument,\n variables: { policyId },\n });\n const revoked = result.data?.revokeDashboardEmbedPolicy ?? null;\n if (revoked) {\n setPolicy(revoked);\n setSecret(null);\n return true;\n }\n return false;\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to unpublish'));\n return false;\n } finally {\n setLoading(false);\n }\n },\n [apolloClient]\n );\n\n return {\n policy,\n secret,\n loading,\n error,\n fetchPolicy,\n publishToWeb,\n applyPresentation,\n setAllowedOrigins,\n rotateKey,\n unpublish,\n };\n}\n\nexport default useDashboardEmbedPolicy;\n"],"mappings":"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"}