@burdenoff/microfe-bigconsole 2026.805.2 → 2026.812.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bigconsole/components/DrilldownDashboardRenderer.js +118 -106
- package/dist/bigconsole/components/DrilldownDashboardRenderer.js.map +1 -1
- package/dist/bigconsole/components/DrilldownModal.js +54 -50
- package/dist/bigconsole/components/DrilldownModal.js.map +1 -1
- package/dist/bigconsole/components/builder/BuilderCanvas.js +2 -2
- package/dist/bigconsole/components/builder/BuilderCanvas.js.map +1 -1
- package/dist/bigconsole/components/builder/BuilderTopbar.js +3 -3
- package/dist/bigconsole/components/builder/BuilderTopbar.js.map +1 -1
- package/dist/bigconsole/components/builder/inspector/AdvancedSection.js +24 -25
- package/dist/bigconsole/components/builder/inspector/AdvancedSection.js.map +1 -1
- package/dist/bigconsole/components/builder/inspector/AppearanceSection.js +40 -29
- package/dist/bigconsole/components/builder/inspector/AppearanceSection.js.map +1 -1
- package/dist/bigconsole/components/builder/inspector/DataBindingSection.js +145 -111
- package/dist/bigconsole/components/builder/inspector/DataBindingSection.js.map +1 -1
- package/dist/bigconsole/components/builder/inspector/GeneralSection.js +58 -44
- package/dist/bigconsole/components/builder/inspector/GeneralSection.js.map +1 -1
- package/dist/bigconsole/components/common/Section.js +70 -0
- package/dist/bigconsole/components/common/Section.js.map +1 -0
- package/dist/bigconsole/components/common/index.js +2 -0
- package/dist/bigconsole/components/common/sectionStyles.js +42 -0
- package/dist/bigconsole/components/common/sectionStyles.js.map +1 -0
- package/dist/bigconsole/components/dashboard/CreateDashboardDialog.js +269 -251
- package/dist/bigconsole/components/dashboard/CreateDashboardDialog.js.map +1 -1
- package/dist/bigconsole/components/dashboard/DashboardCanvas.js +214 -223
- package/dist/bigconsole/components/dashboard/DashboardCanvas.js.map +1 -1
- package/dist/bigconsole/components/dashboard/DashboardGrid.js +12 -18
- package/dist/bigconsole/components/dashboard/DashboardGrid.js.map +1 -1
- package/dist/bigconsole/components/dashboard/DashboardToolbar.js +110 -102
- package/dist/bigconsole/components/dashboard/DashboardToolbar.js.map +1 -1
- package/dist/bigconsole/components/dashboard/DrilldownBreadcrumb.js +81 -68
- package/dist/bigconsole/components/dashboard/DrilldownBreadcrumb.js.map +1 -1
- package/dist/bigconsole/components/dashboard/ExportDashboardDialog.js +125 -159
- package/dist/bigconsole/components/dashboard/ExportDashboardDialog.js.map +1 -1
- package/dist/bigconsole/components/dashboard/GlobalFiltersBar/FilterChip.js +147 -144
- package/dist/bigconsole/components/dashboard/GlobalFiltersBar/FilterChip.js.map +1 -1
- package/dist/bigconsole/components/dashboard/GlobalFiltersBar/FilterEditor.js +154 -153
- package/dist/bigconsole/components/dashboard/GlobalFiltersBar/FilterEditor.js.map +1 -1
- package/dist/bigconsole/components/dashboard/GlobalFiltersBar/FilterPresetSelector.js +82 -79
- package/dist/bigconsole/components/dashboard/GlobalFiltersBar/FilterPresetSelector.js.map +1 -1
- package/dist/bigconsole/components/dashboard/GlobalFiltersBar/GlobalFiltersBar.js +171 -284
- package/dist/bigconsole/components/dashboard/GlobalFiltersBar/GlobalFiltersBar.js.map +1 -1
- package/dist/bigconsole/components/dashboard/ImportDashboardDialog.js +250 -273
- package/dist/bigconsole/components/dashboard/ImportDashboardDialog.js.map +1 -1
- package/dist/bigconsole/components/dashboard/LiveBoardBanner.js +45 -53
- package/dist/bigconsole/components/dashboard/LiveBoardBanner.js.map +1 -1
- package/dist/bigconsole/components/dashboard/PageTabsManager/PageTabsManager.js +157 -148
- package/dist/bigconsole/components/dashboard/PageTabsManager/PageTabsManager.js.map +1 -1
- package/dist/bigconsole/components/dashboard/PublishToStoreDialog.js +75 -83
- package/dist/bigconsole/components/dashboard/PublishToStoreDialog.js.map +1 -1
- package/dist/bigconsole/components/dashboard/SavedViewsDropdown/SavedViewsDropdown.js +167 -161
- package/dist/bigconsole/components/dashboard/SavedViewsDropdown/SavedViewsDropdown.js.map +1 -1
- package/dist/bigconsole/components/dashboard/ShareDialog/EmbedTab.js +181 -190
- package/dist/bigconsole/components/dashboard/ShareDialog/EmbedTab.js.map +1 -1
- package/dist/bigconsole/components/dashboard/ShareDialog/ShareDialog.js +101 -95
- package/dist/bigconsole/components/dashboard/ShareDialog/ShareDialog.js.map +1 -1
- package/dist/bigconsole/components/dashboard/StoreTemplatePicker.js +20 -18
- package/dist/bigconsole/components/dashboard/StoreTemplatePicker.js.map +1 -1
- package/dist/bigconsole/components/dashboard/TemplatePicker/TemplatePicker.js +1 -0
- package/dist/bigconsole/components/dashboard/TemplateSetupBanner.js +26 -58
- package/dist/bigconsole/components/dashboard/TemplateSetupBanner.js.map +1 -1
- package/dist/bigconsole/components/dashboard/WidgetPalette.js +307 -229
- package/dist/bigconsole/components/dashboard/WidgetPalette.js.map +1 -1
- package/dist/bigconsole/components/datasink/CacheTTLSelector.js +128 -129
- package/dist/bigconsole/components/datasink/CacheTTLSelector.js.map +1 -1
- package/dist/bigconsole/components/datasink/DataSinkCard.js +78 -79
- package/dist/bigconsole/components/datasink/DataSinkCard.js.map +1 -1
- package/dist/bigconsole/components/datasink/DataSinkDataViewer.js +33 -34
- package/dist/bigconsole/components/datasink/DataSinkDataViewer.js.map +1 -1
- package/dist/bigconsole/components/datasink/DataSinkDeleteDialog.js +79 -61
- package/dist/bigconsole/components/datasink/DataSinkDeleteDialog.js.map +1 -1
- package/dist/bigconsole/components/datasink/DataSinkRefreshDialog.js +75 -64
- package/dist/bigconsole/components/datasink/DataSinkRefreshDialog.js.map +1 -1
- package/dist/bigconsole/components/datasink/DataSinkStatusBadge.js +1 -1
- package/dist/bigconsole/components/datasink/DataSinkStatusBadge.js.map +1 -1
- package/dist/bigconsole/components/datasink/DataSinkTableViewer.js +20 -19
- package/dist/bigconsole/components/datasink/DataSinkTableViewer.js.map +1 -1
- package/dist/bigconsole/components/datasink/ExportDataSinkDialog.js +127 -171
- package/dist/bigconsole/components/datasink/ExportDataSinkDialog.js.map +1 -1
- package/dist/bigconsole/components/datasink/ImportDataGuideDialog.js +113 -117
- package/dist/bigconsole/components/datasink/ImportDataGuideDialog.js.map +1 -1
- package/dist/bigconsole/components/datasink/ImportDataSinkDialog.js +312 -307
- package/dist/bigconsole/components/datasink/ImportDataSinkDialog.js.map +1 -1
- package/dist/bigconsole/components/datasink/LinkedParsersPanel.js +65 -66
- package/dist/bigconsole/components/datasink/LinkedParsersPanel.js.map +1 -1
- package/dist/bigconsole/components/datasink/LinkedWorkflowsPanel.js +121 -127
- package/dist/bigconsole/components/datasink/LinkedWorkflowsPanel.js.map +1 -1
- package/dist/bigconsole/components/datasink/RefreshTypeSelector.js +194 -189
- package/dist/bigconsole/components/datasink/RefreshTypeSelector.js.map +1 -1
- package/dist/bigconsole/components/datasink/SetupStateBadge.js +3 -3
- package/dist/bigconsole/components/datasink/SetupStateBadge.js.map +1 -1
- package/dist/bigconsole/components/datasink/WorkflowStatusPanel.js +95 -95
- package/dist/bigconsole/components/datasink/WorkflowStatusPanel.js.map +1 -1
- package/dist/bigconsole/components/parser/DataSinkSelector.js +163 -131
- package/dist/bigconsole/components/parser/DataSinkSelector.js.map +1 -1
- package/dist/bigconsole/components/parser/ExpressionEditor.js +3 -3
- package/dist/bigconsole/components/parser/ExpressionEditor.js.map +1 -1
- package/dist/bigconsole/components/parser/ImportParserDialog.js +205 -260
- package/dist/bigconsole/components/parser/ImportParserDialog.js.map +1 -1
- package/dist/bigconsole/components/parser/ParserCard.js +133 -114
- package/dist/bigconsole/components/parser/ParserCard.js.map +1 -1
- package/dist/bigconsole/components/parser/ParserSelector.js +193 -174
- package/dist/bigconsole/components/parser/ParserSelector.js.map +1 -1
- package/dist/bigconsole/components/parser/ParserStatusBadge.js +2 -2
- package/dist/bigconsole/components/parser/ParserStatusBadge.js.map +1 -1
- package/dist/bigconsole/components/parser/ParserTypeBadge.js +4 -4
- package/dist/bigconsole/components/parser/ParserTypeBadge.js.map +1 -1
- package/dist/bigconsole/components/pipeline/PipelineCard.js +134 -89
- package/dist/bigconsole/components/pipeline/PipelineCard.js.map +1 -1
- package/dist/bigconsole/components/pipeline/PipelineDeleteDialog.js +77 -64
- package/dist/bigconsole/components/pipeline/PipelineDeleteDialog.js.map +1 -1
- package/dist/bigconsole/components/pipeline/PipelineStatusBadge.js +7 -10
- package/dist/bigconsole/components/pipeline/PipelineStatusBadge.js.map +1 -1
- package/dist/bigconsole/components/preview/AiPreviewContent.js +31 -32
- package/dist/bigconsole/components/preview/AiPreviewContent.js.map +1 -1
- package/dist/bigconsole/components/preview/AiPreviewMiniPanel.js +152 -103
- package/dist/bigconsole/components/preview/AiPreviewMiniPanel.js.map +1 -1
- package/dist/bigconsole/components/preview/AiPreviewPanel.js +193 -148
- package/dist/bigconsole/components/preview/AiPreviewPanel.js.map +1 -1
- package/dist/bigconsole/components/preview/previewShared.js +41 -23
- package/dist/bigconsole/components/preview/previewShared.js.map +1 -1
- package/dist/bigconsole/components/widgets/ActionConfigDialog.js +405 -364
- package/dist/bigconsole/components/widgets/ActionConfigDialog.js.map +1 -1
- package/dist/bigconsole/components/widgets/DrilldownConfigPanel.js +265 -237
- package/dist/bigconsole/components/widgets/DrilldownConfigPanel.js.map +1 -1
- package/dist/bigconsole/components/widgets/PropertiesPanel.js +764 -821
- package/dist/bigconsole/components/widgets/PropertiesPanel.js.map +1 -1
- package/dist/bigconsole/components/widgets/WidgetErrorBoundary.js +7 -5
- package/dist/bigconsole/components/widgets/WidgetErrorBoundary.js.map +1 -1
- package/dist/bigconsole/components/widgets/WidgetHeader.js +7 -8
- package/dist/bigconsole/components/widgets/WidgetHeader.js.map +1 -1
- package/dist/bigconsole/components/widgets/WidgetWrapper.js +8 -7
- package/dist/bigconsole/components/widgets/WidgetWrapper.js.map +1 -1
- package/dist/bigconsole/components/widgets/adaptive-card/AdaptiveCardWidget.js +1 -1
- package/dist/bigconsole/components/widgets/adaptive-card/AdaptiveCardWidget.js.map +1 -1
- package/dist/bigconsole/components/widgets/calendar/CalendarWidget.js +46 -49
- package/dist/bigconsole/components/widgets/calendar/CalendarWidget.js.map +1 -1
- package/dist/bigconsole/components/widgets/chart/ChartConfig.js +154 -147
- package/dist/bigconsole/components/widgets/chart/ChartConfig.js.map +1 -1
- package/dist/bigconsole/components/widgets/comments/WidgetComments.js +118 -101
- package/dist/bigconsole/components/widgets/comments/WidgetComments.js.map +1 -1
- package/dist/bigconsole/components/widgets/event-logs/EventLogViewer.js +244 -191
- package/dist/bigconsole/components/widgets/event-logs/EventLogViewer.js.map +1 -1
- package/dist/bigconsole/components/widgets/form/FormWidget.js +167 -159
- package/dist/bigconsole/components/widgets/form/FormWidget.js.map +1 -1
- package/dist/bigconsole/components/widgets/gauge/GaugeConfig.js +92 -76
- package/dist/bigconsole/components/widgets/gauge/GaugeConfig.js.map +1 -1
- package/dist/bigconsole/components/widgets/gauge/GaugeWidget.js +1 -1
- package/dist/bigconsole/components/widgets/gauge/GaugeWidget.js.map +1 -1
- package/dist/bigconsole/components/widgets/heatmap/HeatmapConfig.js +89 -86
- package/dist/bigconsole/components/widgets/heatmap/HeatmapConfig.js.map +1 -1
- package/dist/bigconsole/components/widgets/iframe-widget/IframeWidget.js +2 -2
- package/dist/bigconsole/components/widgets/iframe-widget/IframeWidget.js.map +1 -1
- package/dist/bigconsole/components/widgets/kanban/KanbanWidget.js +64 -63
- package/dist/bigconsole/components/widgets/kanban/KanbanWidget.js.map +1 -1
- package/dist/bigconsole/components/widgets/kpi-comparison/KPIComparisonWidget.js +2 -2
- package/dist/bigconsole/components/widgets/kpi-comparison/KPIComparisonWidget.js.map +1 -1
- package/dist/bigconsole/components/widgets/list/ListConfig.js +65 -58
- package/dist/bigconsole/components/widgets/list/ListConfig.js.map +1 -1
- package/dist/bigconsole/components/widgets/list/ListItem.js +1 -1
- package/dist/bigconsole/components/widgets/list/ListItem.js.map +1 -1
- package/dist/bigconsole/components/widgets/map-widget/MapWidget.js +1 -1
- package/dist/bigconsole/components/widgets/map-widget/MapWidget.js.map +1 -1
- package/dist/bigconsole/components/widgets/metric-card/MetricCardConfig.js +79 -72
- package/dist/bigconsole/components/widgets/metric-card/MetricCardConfig.js.map +1 -1
- package/dist/bigconsole/components/widgets/metric-card/SparklineChart.js +35 -34
- package/dist/bigconsole/components/widgets/metric-card/SparklineChart.js.map +1 -1
- package/dist/bigconsole/components/widgets/metric-card/TrendIndicator.js +20 -19
- package/dist/bigconsole/components/widgets/metric-card/TrendIndicator.js.map +1 -1
- package/dist/bigconsole/components/widgets/pivot-table/PivotTableWidget.js +181 -180
- package/dist/bigconsole/components/widgets/pivot-table/PivotTableWidget.js.map +1 -1
- package/dist/bigconsole/components/widgets/progress/ProgressConfig.js +100 -90
- package/dist/bigconsole/components/widgets/progress/ProgressConfig.js.map +1 -1
- package/dist/bigconsole/components/widgets/retention/RetentionWidget.js +3 -3
- package/dist/bigconsole/components/widgets/retention/RetentionWidget.js.map +1 -1
- package/dist/bigconsole/components/widgets/states/EmptyState.js +6 -4
- package/dist/bigconsole/components/widgets/states/EmptyState.js.map +1 -1
- package/dist/bigconsole/components/widgets/states/ErrorState.js +7 -5
- package/dist/bigconsole/components/widgets/states/ErrorState.js.map +1 -1
- package/dist/bigconsole/components/widgets/states/LoadingState.js +5 -4
- package/dist/bigconsole/components/widgets/states/LoadingState.js.map +1 -1
- package/dist/bigconsole/components/widgets/table/TableConfig.js +193 -172
- package/dist/bigconsole/components/widgets/table/TableConfig.js.map +1 -1
- package/dist/bigconsole/components/widgets/table/TableHeader.js +1 -1
- package/dist/bigconsole/components/widgets/table/TableHeader.js.map +1 -1
- package/dist/bigconsole/components/widgets/table/TableWidget.js +1 -1
- package/dist/bigconsole/components/widgets/table/TableWidget.js.map +1 -1
- package/dist/bigconsole/components/widgets/text-widget/TextWidget.js +1 -1
- package/dist/bigconsole/components/widgets/text-widget/TextWidget.js.map +1 -1
- package/dist/bigconsole/components/widgets/timeline/TimelineWidget.js +3 -3
- package/dist/bigconsole/components/widgets/timeline/TimelineWidget.js.map +1 -1
- package/dist/bigconsole/components/widgets/utils/conditionalFormat.js +2 -2
- package/dist/bigconsole/components/widgets/utils/conditionalFormat.js.map +1 -1
- package/dist/bigconsole/components/widgets/webhook-config/WebhookConfigPanel.js +283 -249
- package/dist/bigconsole/components/widgets/webhook-config/WebhookConfigPanel.js.map +1 -1
- package/dist/bigconsole/components/widgets/widget-actions/WidgetActions.js +28 -20
- package/dist/bigconsole/components/widgets/widget-actions/WidgetActions.js.map +1 -1
- package/dist/bigconsole/components/workflow/WorkflowCard.js +92 -77
- package/dist/bigconsole/components/workflow/WorkflowCard.js.map +1 -1
- package/dist/bigconsole/components/workflow/WorkflowDeleteDialog.js +70 -51
- package/dist/bigconsole/components/workflow/WorkflowDeleteDialog.js.map +1 -1
- package/dist/bigconsole/components/workflow/WorkflowStatusBadge.js +16 -17
- package/dist/bigconsole/components/workflow/WorkflowStatusBadge.js.map +1 -1
- package/dist/bigconsole/customWidget/CustomWidgetBuilderSheet.js +46 -42
- package/dist/bigconsole/customWidget/CustomWidgetBuilderSheet.js.map +1 -1
- package/dist/bigconsole/customWidget/CustomWidgetProposalPanel.js +372 -348
- package/dist/bigconsole/customWidget/CustomWidgetProposalPanel.js.map +1 -1
- package/dist/bigconsole/hooks/useModalFocusTrap.js +53 -0
- package/dist/bigconsole/hooks/useModalFocusTrap.js.map +1 -0
- package/dist/bigconsole/pages/DashboardBuilderPage.js +153 -142
- package/dist/bigconsole/pages/DashboardBuilderPage.js.map +1 -1
- package/dist/bigconsole/pages/DashboardViewPage.js +230 -220
- package/dist/bigconsole/pages/DashboardViewPage.js.map +1 -1
- package/dist/bigconsole/pages/DashboardsPage.js +832 -790
- package/dist/bigconsole/pages/DashboardsPage.js.map +1 -1
- package/dist/bigconsole/pages/DataParsersPage.js +249 -304
- package/dist/bigconsole/pages/DataParsersPage.js.map +1 -1
- package/dist/bigconsole/pages/DataSinkBuilderPage.js +324 -349
- package/dist/bigconsole/pages/DataSinkBuilderPage.js.map +1 -1
- package/dist/bigconsole/pages/DataSinkViewPage.js +336 -323
- package/dist/bigconsole/pages/DataSinkViewPage.js.map +1 -1
- package/dist/bigconsole/pages/DataSinksPage.js +356 -458
- package/dist/bigconsole/pages/DataSinksPage.js.map +1 -1
- package/dist/bigconsole/pages/DataSourcesPage.js +14 -8
- package/dist/bigconsole/pages/DataSourcesPage.js.map +1 -1
- package/dist/bigconsole/pages/HomePage.js +186 -193
- package/dist/bigconsole/pages/HomePage.js.map +1 -1
- package/dist/bigconsole/pages/InsightsPage.js +901 -724
- package/dist/bigconsole/pages/InsightsPage.js.map +1 -1
- package/dist/bigconsole/pages/ParserBuilderPage.js +769 -675
- package/dist/bigconsole/pages/ParserBuilderPage.js.map +1 -1
- package/dist/bigconsole/pages/ParserViewPage.js +382 -354
- package/dist/bigconsole/pages/ParserViewPage.js.map +1 -1
- package/dist/bigconsole/pages/PipelineBuilderPage.js +540 -420
- package/dist/bigconsole/pages/PipelineBuilderPage.js.map +1 -1
- package/dist/bigconsole/pages/PipelineViewPage.js +416 -384
- package/dist/bigconsole/pages/PipelineViewPage.js.map +1 -1
- package/dist/bigconsole/pages/PipelinesPage.js +260 -309
- package/dist/bigconsole/pages/PipelinesPage.js.map +1 -1
- package/dist/bigconsole/pages/SettingsPage.js +177 -172
- package/dist/bigconsole/pages/SettingsPage.js.map +1 -1
- package/dist/bigconsole/pages/WidgetDemoPage.js +278 -142
- package/dist/bigconsole/pages/WidgetDemoPage.js.map +1 -1
- package/dist/bigconsole/pages/WorkflowViewPage.js +382 -366
- package/dist/bigconsole/pages/WorkflowViewPage.js.map +1 -1
- package/dist/bigconsole/pages/WorkflowsPage.js +202 -316
- package/dist/bigconsole/pages/WorkflowsPage.js.map +1 -1
- package/dist/bigconsole/renderers/adapters/AdaptiveCardRenderer.js +111 -109
- package/dist/bigconsole/renderers/adapters/AdaptiveCardRenderer.js.map +1 -1
- package/dist/bigconsole/renderers/adapters/BlockKitRenderer.js +127 -126
- package/dist/bigconsole/renderers/adapters/BlockKitRenderer.js.map +1 -1
- package/dist/bigconsole/renderers/adapters/CustomRenderer.js +103 -103
- package/dist/bigconsole/renderers/adapters/CustomRenderer.js.map +1 -1
- package/package.json +2 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EmbedTab.js","names":[],"sources":["../../../../../src/bigconsole/components/dashboard/ShareDialog/EmbedTab.tsx"],"sourcesContent":["/**\n * EmbedTab — \"Publish to web\" section of the dashboard Share dialog.\n *\n * Owner-facing UI to publish a dashboard as an anonymous-link embed serving LIVE\n * data, manage the allowed parent domains, copy the iframe snippet, rotate the\n * link key, and unpublish. Backed by {@link useDashboardEmbedPolicy}; all\n * mutations are gateway-enforced by `@rbac(manage_embed)` (owner-only).\n *\n * Self-contained (inline styles driven by the dialog's `colors`) so it slots into\n * the existing hand-styled ShareDialog without a broader refactor.\n */\n\nimport React, { useEffect, useMemo, useRef, useState } from 'react';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useDashboardEmbedPolicy, themeChoiceFromPolicy } from '../../../hooks/useDashboardEmbedPolicy';\nimport { BIGCONSOLE_PERMISSIONS } from '../../../../constants/permissions';\n\ninterface DialogColors {\n bgPrimary: string;\n bgSecondary: string;\n bgTertiary: string;\n textPrimary: string;\n textSecondary: string;\n textTertiary: string;\n borderDefault: string;\n actionPrimaryBg: string;\n actionPrimaryBgHover: string;\n statusSuccessBg: string;\n statusErrorText: string;\n}\n\nexport interface EmbedTabProps {\n dashboardId: string;\n colors: DialogColors;\n}\n\n/** Embed color-mode baked into the iframe URL (BOFF-5734). */\ntype EmbedSnippetTheme = 'light' | 'dark' | 'auto';\n\nconst THEME_OPTIONS: ReadonlyArray<{ value: EmbedSnippetTheme; label: string }> = [\n { value: 'light', label: 'Light' },\n { value: 'dark', label: 'Dark' },\n { value: 'auto', label: 'Auto (match viewer)' },\n];\n\n/**\n * Bake the chosen color mode into an iframe snippet by adding `?theme=` to its\n * `src` URL. `light` is the default and is left implicit so existing/plain\n * snippets stay unchanged. The secret `#key=` fragment is preserved untouched.\n *\n * Inserted via string splicing (before the fragment) rather than `URL`/\n * `URLSearchParams`, which re-serialize ALL params and could subtly re-encode\n * existing ones (e.g. `%20` → `+`). `theme` is a fixed enum, so no encoding is\n * needed; every other part of the URL is passed through byte-for-byte.\n */\nexport function withThemeInSnippet(snippet: string, theme: EmbedSnippetTheme): string {\n if (theme === 'light') return snippet;\n // Require whitespace before `src` so we match the real attribute, never a\n // substring like `data-src`/`srcdoc` (the snippet is backend-generated with\n // each attribute on its own indented line).\n return snippet.replace(/(?<=\\s)src=\"([^\"]*)\"/, (_match, url: string) => {\n const hashIdx = url.indexOf('#');\n const base = hashIdx === -1 ? url : url.slice(0, hashIdx);\n const fragment = hashIdx === -1 ? '' : url.slice(hashIdx);\n const sep = base.includes('?') ? '&' : '?';\n return `src=\"${base}${sep}theme=${theme}${fragment}\"`;\n });\n}\n\n/** Validate an exact https origin (matches the backend allowlist rules). */\nfunction normalizeOrigin(raw: string): string | null {\n const trimmed = raw.trim();\n if (!trimmed) return null;\n try {\n const url = new URL(trimmed);\n if (url.protocol !== 'https:') return null;\n if ((url.pathname !== '' && url.pathname !== '/') || url.search || url.hash) return null;\n return url.origin;\n } catch {\n return null;\n }\n}\n\nexport function EmbedTab({ dashboardId, colors }: EmbedTabProps) {\n const {\n policy,\n secret,\n loading,\n error,\n fetchPolicy,\n publishToWeb,\n applyPresentation,\n setAllowedOrigins,\n rotateKey,\n unpublish,\n } = useDashboardEmbedPolicy();\n\n // All embed policy mutations (publish, rotate, revoke, set-origins, apply\n // presentation) are gateway-enforced by `@rbac(manage_embed)`. Gate the UI on\n // the same permission so the controls degrade gracefully instead of erroring.\n const { hasPermission } = usePermissions();\n const canManageEmbed = hasPermission(BIGCONSOLE_PERMISSIONS.DASHBOARD_MANAGE_EMBED);\n\n const [domainInput, setDomainInput] = useState('');\n const [pendingDomains, setPendingDomains] = useState<string[]>([]);\n const [attested, setAttested] = useState(false);\n const [copied, setCopied] = useState(false);\n // Presentation options (BOFF-5735). Defaults mirror the backend column defaults\n // so a fresh publish captures a sensible look without extra clicks.\n const [theme, setTheme] = useState<EmbedSnippetTheme>('light');\n const [showTitle, setShowTitle] = useState(false);\n const [showBranding, setShowBranding] = useState(true);\n const [presentationSaved, setPresentationSaved] = useState(false);\n const [localError, setLocalError] = useState<string | null>(null);\n // Only seed the presentation controls once per policy identity — NOT on every\n // `policy` object change. Otherwise an unrelated mutation (e.g. adding a domain\n // via setAllowedOrigins) would return a fresh policy object and clobber the\n // owner's in-progress, not-yet-applied theme/title/branding edits.\n const syncedPolicyIdRef = useRef<string | null>(null);\n const savedTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n if (dashboardId) fetchPolicy(dashboardId);\n }, [dashboardId, fetchPolicy]);\n\n // Reflect the persisted presentation the first time each policy loads.\n useEffect(() => {\n if (!policy || syncedPolicyIdRef.current === policy.id) return;\n syncedPolicyIdRef.current = policy.id;\n setTheme(themeChoiceFromPolicy(policy.presentationTheme));\n setShowTitle(policy.showDashboardTitle);\n setShowBranding(policy.showBrandingFooter);\n }, [policy]);\n\n // Clear any pending \"Updated ✓\" timer on unmount.\n useEffect(\n () => () => {\n if (savedTimerRef.current) clearTimeout(savedTimerRef.current);\n },\n []\n );\n\n const isPublished = policy?.status === 'ACTIVE';\n const domains = isPublished ? (policy?.allowedParentOrigins ?? []) : pendingDomains;\n\n // The copyable iframe snippet: from the one-time secret if present (fresh\n // publish/rotate), else reconstructed from the locator for an already-published\n // policy (the fragment key is only available in the one-time secret).\n const iframeSnippet = useMemo(() => {\n if (secret?.iframeSnippet) return withThemeInSnippet(secret.iframeSnippet, theme);\n return null;\n }, [secret, theme]);\n\n const addDomain = () => {\n setLocalError(null);\n const origin = normalizeOrigin(domainInput);\n if (!origin) {\n setLocalError('Enter a valid https origin, e.g. https://example.com');\n return;\n }\n if (domains.includes(origin)) {\n setDomainInput('');\n return;\n }\n if (isPublished && policy) {\n void setAllowedOrigins(policy.id, [...domains, origin]);\n } else {\n setPendingDomains((prev) => [...prev, origin]);\n }\n setDomainInput('');\n };\n\n const removeDomain = (origin: string) => {\n if (isPublished && policy) {\n void setAllowedOrigins(\n policy.id,\n domains.filter((d) => d !== origin)\n );\n } else {\n setPendingDomains((prev) => prev.filter((d) => d !== origin));\n }\n };\n\n const handlePublish = async () => {\n setLocalError(null);\n if (pendingDomains.length === 0) {\n setLocalError('Add at least one allowed website domain before publishing.');\n return;\n }\n if (!attested) {\n setLocalError('Please confirm this dashboard is safe to be public.');\n return;\n }\n await publishToWeb(dashboardId, pendingDomains, true, { theme, showTitle, showBranding });\n };\n\n const handleApplyPresentation = async () => {\n if (!policy) return;\n setLocalError(null);\n const updated = await applyPresentation(policy.id, { theme, showTitle, showBranding });\n if (updated) {\n setPresentationSaved(true);\n // Clear any in-flight timer so rapid re-clicks don't hide the confirmation early.\n if (savedTimerRef.current) clearTimeout(savedTimerRef.current);\n savedTimerRef.current = setTimeout(() => setPresentationSaved(false), 1500);\n }\n };\n\n const handleCopy = async (text: string) => {\n try {\n await navigator.clipboard.writeText(text);\n setCopied(true);\n setTimeout(() => setCopied(false), 1500);\n } catch {\n setLocalError('Could not copy — select and copy manually.');\n }\n };\n\n const primaryBtn: React.CSSProperties = {\n padding: '8px 14px',\n borderRadius: '8px',\n border: 'none',\n background: colors.actionPrimaryBg,\n color: '#fff',\n fontSize: '13px',\n fontWeight: 600,\n cursor: 'pointer',\n };\n const ghostBtn: React.CSSProperties = {\n padding: '8px 14px',\n borderRadius: '8px',\n border: `1px solid ${colors.borderDefault}`,\n background: 'transparent',\n color: colors.textPrimary,\n fontSize: '13px',\n cursor: 'pointer',\n };\n const inputStyle: React.CSSProperties = {\n flex: 1,\n padding: '8px 10px',\n borderRadius: '8px',\n border: `1px solid ${colors.borderDefault}`,\n background: colors.bgSecondary,\n color: colors.textPrimary,\n fontSize: '13px',\n };\n const selectStyle: React.CSSProperties = {\n padding: '4px 8px',\n borderRadius: '6px',\n border: `1px solid ${colors.borderDefault}`,\n background: colors.bgSecondary,\n color: colors.textPrimary,\n fontSize: '12px',\n cursor: 'pointer',\n };\n const toggleRow: React.CSSProperties = {\n display: 'flex',\n gap: '8px',\n alignItems: 'flex-start',\n fontSize: '12px',\n color: colors.textSecondary,\n };\n\n // Presentation controls (BOFF-5735) — shared by the pre-publish and published\n // states. Local state drives both the persisted policy and the snippet `?theme=`.\n const presentationControls = (\n <div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>\n <label style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '8px' }}>\n <span style={{ fontSize: '12px', color: colors.textSecondary }}>Theme</span>\n <select\n value={theme}\n onChange={(e) => {\n // The options are fixed, but narrow instead of casting so an\n // unexpected value can never slip into state.\n const value = e.target.value;\n if (value === 'light' || value === 'dark' || value === 'auto') {\n setTheme(value);\n }\n }}\n style={selectStyle}\n >\n {THEME_OPTIONS.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n </label>\n <label style={toggleRow}>\n <input type=\"checkbox\" checked={showTitle} onChange={(e) => setShowTitle(e.target.checked)} />\n <span>Show the dashboard title as a header</span>\n </label>\n <label style={toggleRow}>\n <input type=\"checkbox\" checked={showBranding} onChange={(e) => setShowBranding(e.target.checked)} />\n <span>Show a subtle “Powered by BigConsole” footer</span>\n </label>\n </div>\n );\n\n return (\n <div style={{ padding: '20px 24px' }}>\n {/* Status banner */}\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n padding: '12px 14px',\n borderRadius: '10px',\n background: colors.bgSecondary,\n marginBottom: '16px',\n }}\n >\n <div>\n <div style={{ fontSize: '14px', fontWeight: 600, color: colors.textPrimary }}>Publish to the web</div>\n <div style={{ fontSize: '12px', color: colors.textSecondary, marginTop: '2px' }}>\n {isPublished\n ? 'This dashboard is live. Anyone with the link on an allowed website sees its live data.'\n : 'Embed this dashboard on your website. Viewers see live data — no login required.'}\n </div>\n </div>\n <span\n style={{\n fontSize: '11px',\n fontWeight: 700,\n padding: '3px 8px',\n borderRadius: '999px',\n color: '#fff',\n background: isPublished ? colors.statusSuccessBg : colors.textTertiary,\n }}\n >\n {isPublished ? 'PUBLISHED' : 'PRIVATE'}\n </span>\n </div>\n\n {/* Allowed domains */}\n <div style={{ marginBottom: '16px' }}>\n <div style={{ fontSize: '12px', fontWeight: 600, color: colors.textSecondary, marginBottom: '6px' }}>\n Allowed website domains\n </div>\n <div style={{ display: 'flex', gap: '8px', marginBottom: '8px' }}>\n <input\n style={inputStyle}\n placeholder=\"https://yoursite.com\"\n value={domainInput}\n onChange={(e) => setDomainInput(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') addDomain();\n }}\n />\n <button style={ghostBtn} onClick={addDomain} type=\"button\" disabled={!canManageEmbed}>\n Add\n </button>\n </div>\n {domains.length === 0 ? (\n <div style={{ fontSize: '12px', color: colors.textTertiary }}>\n No domains yet. Only listed https domains may frame this dashboard.\n </div>\n ) : (\n <div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>\n {domains.map((d) => (\n <div\n key={d}\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n padding: '6px 10px',\n borderRadius: '8px',\n background: colors.bgTertiary,\n }}\n >\n <span style={{ fontSize: '13px', color: colors.textPrimary }}>{d}</span>\n {canManageEmbed && (\n <button\n style={{ ...ghostBtn, padding: '2px 8px', color: colors.statusErrorText }}\n onClick={() => removeDomain(d)}\n type=\"button\"\n >\n Remove\n </button>\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n\n {/* Presentation (unpublished — captured into the publish) */}\n {!isPublished && (\n <div style={{ marginBottom: '16px' }}>\n <div style={{ fontSize: '12px', fontWeight: 600, color: colors.textSecondary, marginBottom: '8px' }}>\n Presentation\n </div>\n {presentationControls}\n </div>\n )}\n\n {/* Publish CTA (unpublished) */}\n {!isPublished && (\n <div style={{ marginBottom: '8px' }}>\n <label style={{ display: 'flex', gap: '8px', alignItems: 'flex-start', marginBottom: '12px' }}>\n <input type=\"checkbox\" checked={attested} onChange={(e) => setAttested(e.target.checked)} />\n <span style={{ fontSize: '12px', color: colors.textSecondary }}>\n I understand this makes the dashboard's live data visible to anyone with the link, and I confirm it\n is safe to be public.\n </span>\n </label>\n <button style={primaryBtn} onClick={handlePublish} disabled={loading || !canManageEmbed} type=\"button\">\n {loading ? 'Publishing…' : 'Publish to web'}\n </button>\n </div>\n )}\n\n {/* Embed snippet + management (published) */}\n {isPublished && (\n <>\n {iframeSnippet ? (\n <div style={{ marginBottom: '16px' }}>\n <div style={{ marginBottom: '6px' }}>\n <span style={{ fontSize: '12px', fontWeight: 600, color: colors.textSecondary }}>Embed snippet</span>\n </div>\n <textarea\n readOnly\n value={iframeSnippet}\n style={{\n width: '100%',\n minHeight: '96px',\n padding: '10px',\n borderRadius: '8px',\n border: `1px solid ${colors.borderDefault}`,\n background: colors.bgSecondary,\n color: colors.textPrimary,\n fontFamily: 'monospace',\n fontSize: '12px',\n resize: 'vertical',\n }}\n />\n <button style={{ ...ghostBtn, marginTop: '8px' }} onClick={() => handleCopy(iframeSnippet)} type=\"button\">\n {copied ? 'Copied ✓' : 'Copy snippet'}\n </button>\n </div>\n ) : (\n <div\n style={{\n fontSize: '12px',\n color: colors.textSecondary,\n background: colors.bgSecondary,\n borderRadius: '8px',\n padding: '10px 12px',\n marginBottom: '16px',\n }}\n >\n The embed link is already active. For security the snippet (which contains the secret key) is only shown\n once at publish time — use <strong>Rotate link</strong> to generate a fresh snippet.\n </div>\n )}\n\n {/* Presentation (published — persisted + re-published to take effect) */}\n <div style={{ marginBottom: '16px' }}>\n <div style={{ fontSize: '12px', fontWeight: 600, color: colors.textSecondary, marginBottom: '8px' }}>\n Presentation\n </div>\n {presentationControls}\n <div style={{ display: 'flex', alignItems: 'center', gap: '10px', marginTop: '10px' }}>\n <button\n style={ghostBtn}\n onClick={handleApplyPresentation}\n disabled={loading || !canManageEmbed}\n type=\"button\"\n >\n {loading ? 'Applying…' : 'Apply changes'}\n </button>\n {presentationSaved && <span style={{ fontSize: '12px', color: colors.textTertiary }}>Updated ✓</span>}\n </div>\n <div style={{ fontSize: '11px', color: colors.textTertiary, marginTop: '6px' }}>\n Changes apply to live embeds within a few seconds — no re-publish needed, and the embed link stays the\n same.\n </div>\n </div>\n\n {/* Rotate/Unpublish are destructive management actions — hide entirely\n when the caller can't manage the embed. */}\n {canManageEmbed && (\n <div style={{ display: 'flex', gap: '8px' }}>\n <button style={ghostBtn} onClick={() => policy && rotateKey(policy.id)} disabled={loading} type=\"button\">\n Rotate link\n </button>\n <button\n style={{ ...ghostBtn, color: colors.statusErrorText, borderColor: colors.statusErrorText }}\n onClick={() => {\n if (\n policy &&\n window.confirm(\n 'Unpublish this dashboard? The embed will stop working immediately for anyone using the link.'\n )\n ) {\n void unpublish(policy.id);\n }\n }}\n disabled={loading}\n type=\"button\"\n >\n Unpublish\n </button>\n </div>\n )}\n </>\n )}\n\n {(localError || error) && (\n <div style={{ marginTop: '12px', fontSize: '12px', color: colors.statusErrorText }}>\n {localError ?? error?.message}\n </div>\n )}\n </div>\n );\n}\n\nexport default EmbedTab;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EmbedTab.js","names":[],"sources":["../../../../../src/bigconsole/components/dashboard/ShareDialog/EmbedTab.tsx"],"sourcesContent":["/**\n * EmbedTab — \"Publish to web\" section of the dashboard Share dialog.\n *\n * Owner-facing UI to publish a dashboard as an anonymous-link embed serving LIVE\n * data, manage the allowed parent domains, copy the iframe snippet, rotate the\n * link key, and unpublish. Backed by {@link useDashboardEmbedPolicy}; all\n * mutations are gateway-enforced by `@rbac(manage_embed)` (owner-only).\n *\n * Self-contained (inline styles driven by the dialog's `colors`) so it slots into\n * the existing hand-styled ShareDialog without a broader refactor.\n */\n\nimport React, { useEffect, useMemo, useRef, useState } from 'react';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useDashboardEmbedPolicy, themeChoiceFromPolicy } from '../../../hooks/useDashboardEmbedPolicy';\nimport { BIGCONSOLE_PERMISSIONS } from '../../../../constants/permissions';\n\ninterface DialogColors {\n bgPrimary: string;\n bgSecondary: string;\n bgTertiary: string;\n textPrimary: string;\n textSecondary: string;\n textTertiary: string;\n borderDefault: string;\n actionPrimaryBg: string;\n actionPrimaryBgHover: string;\n statusSuccessBg: string;\n statusErrorText: string;\n}\n\nexport interface EmbedTabProps {\n dashboardId: string;\n colors: DialogColors;\n}\n\n/** Embed color-mode baked into the iframe URL (BOFF-5734). */\ntype EmbedSnippetTheme = 'light' | 'dark' | 'auto';\n\nconst THEME_OPTION_VALUES: ReadonlyArray<EmbedSnippetTheme> = ['light', 'dark', 'auto'];\n\n/**\n * Bake the chosen color mode into an iframe snippet by adding `?theme=` to its\n * `src` URL. `light` is the default and is left implicit so existing/plain\n * snippets stay unchanged. The secret `#key=` fragment is preserved untouched.\n *\n * Inserted via string splicing (before the fragment) rather than `URL`/\n * `URLSearchParams`, which re-serialize ALL params and could subtly re-encode\n * existing ones (e.g. `%20` → `+`). `theme` is a fixed enum, so no encoding is\n * needed; every other part of the URL is passed through byte-for-byte.\n */\nexport function withThemeInSnippet(snippet: string, theme: EmbedSnippetTheme): string {\n if (theme === 'light') return snippet;\n // Require whitespace before `src` so we match the real attribute, never a\n // substring like `data-src`/`srcdoc` (the snippet is backend-generated with\n // each attribute on its own indented line).\n return snippet.replace(/(?<=\\s)src=\"([^\"]*)\"/, (_match, url: string) => {\n const hashIdx = url.indexOf('#');\n const base = hashIdx === -1 ? url : url.slice(0, hashIdx);\n const fragment = hashIdx === -1 ? '' : url.slice(hashIdx);\n const sep = base.includes('?') ? '&' : '?';\n return `src=\"${base}${sep}theme=${theme}${fragment}\"`;\n });\n}\n\n/** Validate an exact https origin (matches the backend allowlist rules). */\nfunction normalizeOrigin(raw: string): string | null {\n const trimmed = raw.trim();\n if (!trimmed) return null;\n try {\n const url = new URL(trimmed);\n if (url.protocol !== 'https:') return null;\n if ((url.pathname !== '' && url.pathname !== '/') || url.search || url.hash) return null;\n return url.origin;\n } catch {\n return null;\n }\n}\n\nexport function EmbedTab({ dashboardId, colors }: EmbedTabProps) {\n const { t } = useI18n();\n const {\n policy,\n secret,\n loading,\n error,\n fetchPolicy,\n publishToWeb,\n applyPresentation,\n setAllowedOrigins,\n rotateKey,\n unpublish,\n } = useDashboardEmbedPolicy();\n\n // All embed policy mutations (publish, rotate, revoke, set-origins, apply\n // presentation) are gateway-enforced by `@rbac(manage_embed)`. Gate the UI on\n // the same permission so the controls degrade gracefully instead of erroring.\n const { hasPermission } = usePermissions();\n const canManageEmbed = hasPermission(BIGCONSOLE_PERMISSIONS.DASHBOARD_MANAGE_EMBED);\n\n const [domainInput, setDomainInput] = useState('');\n const [pendingDomains, setPendingDomains] = useState<string[]>([]);\n const [attested, setAttested] = useState(false);\n const [copied, setCopied] = useState(false);\n // Presentation options (BOFF-5735). Defaults mirror the backend column defaults\n // so a fresh publish captures a sensible look without extra clicks.\n const [theme, setTheme] = useState<EmbedSnippetTheme>('light');\n const [showTitle, setShowTitle] = useState(false);\n const [showBranding, setShowBranding] = useState(true);\n const [presentationSaved, setPresentationSaved] = useState(false);\n const [localError, setLocalError] = useState<string | null>(null);\n // Only seed the presentation controls once per policy identity — NOT on every\n // `policy` object change. Otherwise an unrelated mutation (e.g. adding a domain\n // via setAllowedOrigins) would return a fresh policy object and clobber the\n // owner's in-progress, not-yet-applied theme/title/branding edits.\n const syncedPolicyIdRef = useRef<string | null>(null);\n const savedTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n if (dashboardId) fetchPolicy(dashboardId);\n }, [dashboardId, fetchPolicy]);\n\n // Reflect the persisted presentation the first time each policy loads.\n useEffect(() => {\n if (!policy || syncedPolicyIdRef.current === policy.id) return;\n syncedPolicyIdRef.current = policy.id;\n setTheme(themeChoiceFromPolicy(policy.presentationTheme));\n setShowTitle(policy.showDashboardTitle);\n setShowBranding(policy.showBrandingFooter);\n }, [policy]);\n\n // Clear any pending \"Updated ✓\" timer on unmount.\n useEffect(\n () => () => {\n if (savedTimerRef.current) clearTimeout(savedTimerRef.current);\n },\n []\n );\n\n const isPublished = policy?.status === 'ACTIVE';\n const domains = isPublished ? (policy?.allowedParentOrigins ?? []) : pendingDomains;\n\n // The copyable iframe snippet: from the one-time secret if present (fresh\n // publish/rotate), else reconstructed from the locator for an already-published\n // policy (the fragment key is only available in the one-time secret).\n const iframeSnippet = useMemo(() => {\n if (secret?.iframeSnippet) return withThemeInSnippet(secret.iframeSnippet, theme);\n return null;\n }, [secret, theme]);\n\n const addDomain = () => {\n setLocalError(null);\n const origin = normalizeOrigin(domainInput);\n if (!origin) {\n setLocalError(\n t('bigconsole.dashboards.embed.error.invalidOrigin', 'Enter a valid https origin, e.g. https://example.com')\n );\n return;\n }\n if (domains.includes(origin)) {\n setDomainInput('');\n return;\n }\n if (isPublished && policy) {\n void setAllowedOrigins(policy.id, [...domains, origin]);\n } else {\n setPendingDomains((prev) => [...prev, origin]);\n }\n setDomainInput('');\n };\n\n const removeDomain = (origin: string) => {\n if (isPublished && policy) {\n void setAllowedOrigins(\n policy.id,\n domains.filter((d) => d !== origin)\n );\n } else {\n setPendingDomains((prev) => prev.filter((d) => d !== origin));\n }\n };\n\n const handlePublish = async () => {\n setLocalError(null);\n if (pendingDomains.length === 0) {\n setLocalError(\n t('bigconsole.dashboards.embed.error.noDomains', 'Add at least one allowed website domain before publishing.')\n );\n return;\n }\n if (!attested) {\n setLocalError(\n t('bigconsole.dashboards.embed.error.notAttested', 'Please confirm this dashboard is safe to be public.')\n );\n return;\n }\n await publishToWeb(dashboardId, pendingDomains, true, { theme, showTitle, showBranding });\n };\n\n const handleApplyPresentation = async () => {\n if (!policy) return;\n setLocalError(null);\n const updated = await applyPresentation(policy.id, { theme, showTitle, showBranding });\n if (updated) {\n setPresentationSaved(true);\n // Clear any in-flight timer so rapid re-clicks don't hide the confirmation early.\n if (savedTimerRef.current) clearTimeout(savedTimerRef.current);\n savedTimerRef.current = setTimeout(() => setPresentationSaved(false), 1500);\n }\n };\n\n const handleCopy = async (text: string) => {\n try {\n await navigator.clipboard.writeText(text);\n setCopied(true);\n setTimeout(() => setCopied(false), 1500);\n } catch {\n setLocalError(t('bigconsole.dashboards.embed.error.copyFailed', 'Could not copy — select and copy manually.'));\n }\n };\n\n const primaryBtn: React.CSSProperties = {\n padding: '8px 14px',\n borderRadius: '8px',\n border: 'none',\n background: colors.actionPrimaryBg,\n color: '#fff',\n fontSize: '13px',\n fontWeight: 600,\n cursor: 'pointer',\n };\n const ghostBtn: React.CSSProperties = {\n padding: '8px 14px',\n borderRadius: '8px',\n border: `1px solid ${colors.borderDefault}`,\n background: 'transparent',\n color: colors.textPrimary,\n fontSize: '13px',\n cursor: 'pointer',\n };\n const inputStyle: React.CSSProperties = {\n flex: 1,\n padding: '8px 10px',\n borderRadius: '8px',\n border: `1px solid ${colors.borderDefault}`,\n background: colors.bgSecondary,\n color: colors.textPrimary,\n fontSize: '13px',\n };\n const selectStyle: React.CSSProperties = {\n padding: '4px 8px',\n borderRadius: '6px',\n border: `1px solid ${colors.borderDefault}`,\n background: colors.bgSecondary,\n color: colors.textPrimary,\n fontSize: '12px',\n cursor: 'pointer',\n };\n const toggleRow: React.CSSProperties = {\n display: 'flex',\n gap: '8px',\n alignItems: 'flex-start',\n fontSize: '12px',\n color: colors.textSecondary,\n };\n\n const themeOptions = useMemo(\n () =>\n THEME_OPTION_VALUES.map((value) => ({\n value,\n label:\n value === 'light'\n ? t('bigconsole.dashboards.embed.theme.option.light', 'Light')\n : value === 'dark'\n ? t('bigconsole.dashboards.embed.theme.option.dark', 'Dark')\n : t('bigconsole.dashboards.embed.theme.option.auto', 'Auto (match viewer)'),\n })),\n [t]\n );\n\n // Presentation controls (BOFF-5735) — shared by the pre-publish and published\n // states. Local state drives both the persisted policy and the snippet `?theme=`.\n const presentationControls = (\n <div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>\n <label style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '8px' }}>\n <span style={{ fontSize: '12px', color: colors.textSecondary }}>\n {t('bigconsole.dashboards.embed.theme.label', 'Theme')}\n </span>\n <select\n value={theme}\n onChange={(e) => {\n // The options are fixed, but narrow instead of casting so an\n // unexpected value can never slip into state.\n const value = e.target.value;\n if (value === 'light' || value === 'dark' || value === 'auto') {\n setTheme(value);\n }\n }}\n style={selectStyle}\n >\n {themeOptions.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n </label>\n <label style={toggleRow}>\n <input type=\"checkbox\" checked={showTitle} onChange={(e) => setShowTitle(e.target.checked)} />\n <span>{t('bigconsole.dashboards.embed.presentation.showTitle', 'Show the dashboard title as a header')}</span>\n </label>\n <label style={toggleRow}>\n <input type=\"checkbox\" checked={showBranding} onChange={(e) => setShowBranding(e.target.checked)} />\n <span>\n {t('bigconsole.dashboards.embed.presentation.showBranding', 'Show a subtle “Powered by BigConsole” footer')}\n </span>\n </label>\n </div>\n );\n\n return (\n <div style={{ padding: '20px 24px' }}>\n {/* Status banner */}\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n padding: '12px 14px',\n borderRadius: '10px',\n background: colors.bgSecondary,\n marginBottom: '16px',\n }}\n >\n <div>\n <div style={{ fontSize: '14px', fontWeight: 600, color: colors.textPrimary }}>\n {t('bigconsole.dashboards.embed.heading', 'Publish to the web')}\n </div>\n <div style={{ fontSize: '12px', color: colors.textSecondary, marginTop: '2px' }}>\n {isPublished\n ? t(\n 'bigconsole.dashboards.embed.status.liveDescription',\n 'This dashboard is live. Anyone with the link on an allowed website sees its live data.'\n )\n : t(\n 'bigconsole.dashboards.embed.status.privateDescription',\n 'Embed this dashboard on your website. Viewers see live data — no login required.'\n )}\n </div>\n </div>\n <span\n style={{\n fontSize: '11px',\n fontWeight: 700,\n padding: '3px 8px',\n borderRadius: '999px',\n color: '#fff',\n background: isPublished ? colors.statusSuccessBg : colors.textTertiary,\n }}\n >\n {isPublished\n ? t('bigconsole.dashboards.embed.badge.published', 'PUBLISHED')\n : t('bigconsole.dashboards.embed.badge.private', 'PRIVATE')}\n </span>\n </div>\n\n {/* Allowed domains */}\n <div style={{ marginBottom: '16px' }}>\n <div style={{ fontSize: '12px', fontWeight: 600, color: colors.textSecondary, marginBottom: '6px' }}>\n {t('bigconsole.dashboards.embed.domains.label', 'Allowed website domains')}\n </div>\n <div style={{ display: 'flex', gap: '8px', marginBottom: '8px' }}>\n <input\n style={inputStyle}\n placeholder={t('bigconsole.dashboards.embed.domains.placeholder', 'https://yoursite.com')}\n value={domainInput}\n onChange={(e) => setDomainInput(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') addDomain();\n }}\n />\n <button style={ghostBtn} onClick={addDomain} type=\"button\" disabled={!canManageEmbed}>\n {t('bigconsole.dashboards.embed.domains.add', 'Add')}\n </button>\n </div>\n {domains.length === 0 ? (\n <div style={{ fontSize: '12px', color: colors.textTertiary }}>\n {t(\n 'bigconsole.dashboards.embed.domains.empty',\n 'No domains yet. Only listed https domains may frame this dashboard.'\n )}\n </div>\n ) : (\n <div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>\n {domains.map((d) => (\n <div\n key={d}\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n padding: '6px 10px',\n borderRadius: '8px',\n background: colors.bgTertiary,\n }}\n >\n <span style={{ fontSize: '13px', color: colors.textPrimary }}>{d}</span>\n {canManageEmbed && (\n <button\n style={{ ...ghostBtn, padding: '2px 8px', color: colors.statusErrorText }}\n onClick={() => removeDomain(d)}\n type=\"button\"\n >\n {t('bigconsole.dashboards.embed.domains.remove', 'Remove')}\n </button>\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n\n {/* Presentation (unpublished — captured into the publish) */}\n {!isPublished && (\n <div style={{ marginBottom: '16px' }}>\n <div style={{ fontSize: '12px', fontWeight: 600, color: colors.textSecondary, marginBottom: '8px' }}>\n {t('bigconsole.dashboards.embed.presentation.heading', 'Presentation')}\n </div>\n {presentationControls}\n </div>\n )}\n\n {/* Publish CTA (unpublished) */}\n {!isPublished && (\n <div style={{ marginBottom: '8px' }}>\n <label style={{ display: 'flex', gap: '8px', alignItems: 'flex-start', marginBottom: '12px' }}>\n <input type=\"checkbox\" checked={attested} onChange={(e) => setAttested(e.target.checked)} />\n <span style={{ fontSize: '12px', color: colors.textSecondary }}>\n {t(\n 'bigconsole.dashboards.embed.attestation',\n 'I understand this makes the dashboard’s live data visible to anyone with the link, and I confirm it is safe to be public.'\n )}\n </span>\n </label>\n <button style={primaryBtn} onClick={handlePublish} disabled={loading || !canManageEmbed} type=\"button\">\n {loading\n ? t('bigconsole.dashboards.embed.publishing', 'Publishing…')\n : t('bigconsole.dashboards.embed.publish', 'Publish to web')}\n </button>\n </div>\n )}\n\n {/* Embed snippet + management (published) */}\n {isPublished && (\n <>\n {iframeSnippet ? (\n <div style={{ marginBottom: '16px' }}>\n <div style={{ marginBottom: '6px' }}>\n <span style={{ fontSize: '12px', fontWeight: 600, color: colors.textSecondary }}>\n {t('bigconsole.dashboards.embed.snippet.label', 'Embed snippet')}\n </span>\n </div>\n <textarea\n readOnly\n value={iframeSnippet}\n style={{\n width: '100%',\n minHeight: '96px',\n padding: '10px',\n borderRadius: '8px',\n border: `1px solid ${colors.borderDefault}`,\n background: colors.bgSecondary,\n color: colors.textPrimary,\n fontFamily: 'monospace',\n fontSize: '12px',\n resize: 'vertical',\n }}\n />\n <button style={{ ...ghostBtn, marginTop: '8px' }} onClick={() => handleCopy(iframeSnippet)} type=\"button\">\n {copied\n ? t('bigconsole.dashboards.embed.snippet.copied', 'Copied ✓')\n : t('bigconsole.dashboards.embed.snippet.copy', 'Copy snippet')}\n </button>\n </div>\n ) : (\n <div\n style={{\n fontSize: '12px',\n color: colors.textSecondary,\n background: colors.bgSecondary,\n borderRadius: '8px',\n padding: '10px 12px',\n marginBottom: '16px',\n }}\n >\n {t(\n 'bigconsole.dashboards.embed.snippet.alreadyActive',\n 'The embed link is already active. For security the snippet (which contains the secret key) is only shown once at publish time — use {{rotateLabel}} to generate a fresh snippet.',\n { rotateLabel: t('bigconsole.dashboards.embed.rotate', 'Rotate link') }\n )}\n </div>\n )}\n\n {/* Presentation (published — persisted + re-published to take effect) */}\n <div style={{ marginBottom: '16px' }}>\n <div style={{ fontSize: '12px', fontWeight: 600, color: colors.textSecondary, marginBottom: '8px' }}>\n {t('bigconsole.dashboards.embed.presentation.heading', 'Presentation')}\n </div>\n {presentationControls}\n <div style={{ display: 'flex', alignItems: 'center', gap: '10px', marginTop: '10px' }}>\n <button\n style={ghostBtn}\n onClick={handleApplyPresentation}\n disabled={loading || !canManageEmbed}\n type=\"button\"\n >\n {loading\n ? t('bigconsole.dashboards.embed.applying', 'Applying…')\n : t('bigconsole.dashboards.embed.apply', 'Apply changes')}\n </button>\n {presentationSaved && (\n <span style={{ fontSize: '12px', color: colors.textTertiary }}>\n {t('bigconsole.dashboards.embed.updated', 'Updated ✓')}\n </span>\n )}\n </div>\n <div style={{ fontSize: '11px', color: colors.textTertiary, marginTop: '6px' }}>\n {t(\n 'bigconsole.dashboards.embed.presentation.applyHelp',\n 'Changes apply to live embeds within a few seconds — no re-publish needed, and the embed link stays the same.'\n )}\n </div>\n </div>\n\n {/* Rotate/Unpublish are destructive management actions — hide entirely\n when the caller can't manage the embed. */}\n {canManageEmbed && (\n <div style={{ display: 'flex', gap: '8px' }}>\n <button style={ghostBtn} onClick={() => policy && rotateKey(policy.id)} disabled={loading} type=\"button\">\n {t('bigconsole.dashboards.embed.rotate', 'Rotate link')}\n </button>\n <button\n style={{ ...ghostBtn, color: colors.statusErrorText, borderColor: colors.statusErrorText }}\n onClick={() => {\n if (\n policy &&\n window.confirm(\n t(\n 'bigconsole.dashboards.embed.unpublishConfirm',\n 'Unpublish this dashboard? The embed will stop working immediately for anyone using the link.'\n )\n )\n ) {\n void unpublish(policy.id);\n }\n }}\n disabled={loading}\n type=\"button\"\n >\n {t('bigconsole.dashboards.embed.unpublish', 'Unpublish')}\n </button>\n </div>\n )}\n </>\n )}\n\n {(localError || error) && (\n <div style={{ marginTop: '12px', fontSize: '12px', color: colors.statusErrorText }}>\n {localError ?? error?.message}\n </div>\n )}\n </div>\n );\n}\n\nexport default EmbedTab;\n"],"mappings":"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"}
|
|
@@ -100,21 +100,30 @@ function f({ dashboardId: f, dashboardName: m, isOpen: h, onClose: g }) {
|
|
|
100
100
|
left: 0,
|
|
101
101
|
right: 0,
|
|
102
102
|
bottom: 0,
|
|
103
|
-
backgroundColor: "
|
|
103
|
+
backgroundColor: "var(--color-overlay-scrim)",
|
|
104
104
|
display: "flex",
|
|
105
105
|
alignItems: "center",
|
|
106
106
|
justifyContent: "center",
|
|
107
|
+
padding: "var(--space-formGap)",
|
|
107
108
|
zIndex: 99999,
|
|
108
109
|
isolation: "isolate"
|
|
109
110
|
},
|
|
110
111
|
onClick: g,
|
|
112
|
+
role: "presentation",
|
|
111
113
|
children: /* @__PURE__ */ c("div", {
|
|
114
|
+
role: "dialog",
|
|
115
|
+
"aria-modal": "true",
|
|
116
|
+
"aria-labelledby": "share-dialog-title",
|
|
117
|
+
"data-testid": "share-dialog",
|
|
112
118
|
style: {
|
|
113
|
-
width: "
|
|
114
|
-
|
|
119
|
+
width: "100%",
|
|
120
|
+
maxWidth: "520px",
|
|
121
|
+
maxHeight: "85vh",
|
|
122
|
+
display: "flex",
|
|
123
|
+
flexDirection: "column",
|
|
115
124
|
backgroundColor: G.bgPrimary,
|
|
116
|
-
borderRadius: "
|
|
117
|
-
boxShadow: "
|
|
125
|
+
borderRadius: "var(--radius-card)",
|
|
126
|
+
boxShadow: "var(--shadow-elevation-3)",
|
|
118
127
|
overflow: "hidden",
|
|
119
128
|
position: "relative",
|
|
120
129
|
zIndex: 1e5,
|
|
@@ -124,6 +133,7 @@ function f({ dashboardId: f, dashboardName: m, isOpen: h, onClose: g }) {
|
|
|
124
133
|
children: [
|
|
125
134
|
/* @__PURE__ */ c("div", {
|
|
126
135
|
style: {
|
|
136
|
+
flexShrink: 0,
|
|
127
137
|
padding: "20px 24px",
|
|
128
138
|
borderBottom: `1px solid ${G.borderDefault}`,
|
|
129
139
|
display: "flex",
|
|
@@ -131,7 +141,8 @@ function f({ dashboardId: f, dashboardName: m, isOpen: h, onClose: g }) {
|
|
|
131
141
|
justifyContent: "space-between",
|
|
132
142
|
backgroundColor: G.bgPrimary
|
|
133
143
|
},
|
|
134
|
-
children: [/* @__PURE__ */ c("div", { children: [/* @__PURE__ */ s("
|
|
144
|
+
children: [/* @__PURE__ */ c("div", { children: [/* @__PURE__ */ s("h2", {
|
|
145
|
+
id: "share-dialog-title",
|
|
135
146
|
style: {
|
|
136
147
|
margin: 0,
|
|
137
148
|
fontSize: "18px",
|
|
@@ -161,7 +172,10 @@ function f({ dashboardId: f, dashboardName: m, isOpen: h, onClose: g }) {
|
|
|
161
172
|
})]
|
|
162
173
|
}),
|
|
163
174
|
/* @__PURE__ */ s("div", {
|
|
175
|
+
role: "tablist",
|
|
176
|
+
"aria-label": "Share options",
|
|
164
177
|
style: {
|
|
178
|
+
flexShrink: 0,
|
|
165
179
|
display: "flex",
|
|
166
180
|
gap: "4px",
|
|
167
181
|
padding: "0 24px",
|
|
@@ -170,6 +184,8 @@ function f({ dashboardId: f, dashboardName: m, isOpen: h, onClose: g }) {
|
|
|
170
184
|
},
|
|
171
185
|
children: ["people", "embed"].map((e) => /* @__PURE__ */ s("button", {
|
|
172
186
|
type: "button",
|
|
187
|
+
role: "tab",
|
|
188
|
+
"aria-selected": I === e,
|
|
173
189
|
onClick: () => L(e),
|
|
174
190
|
style: {
|
|
175
191
|
padding: "10px 12px",
|
|
@@ -185,39 +201,29 @@ function f({ dashboardId: f, dashboardName: m, isOpen: h, onClose: g }) {
|
|
|
185
201
|
}, e))
|
|
186
202
|
}),
|
|
187
203
|
I === "embed" && /* @__PURE__ */ s("div", {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
maxHeight: "calc(80vh - 160px)",
|
|
191
|
-
backgroundColor: G.bgPrimary
|
|
192
|
-
},
|
|
204
|
+
className: "min-h-0 flex-1 overflow-y-auto",
|
|
205
|
+
style: { backgroundColor: G.bgPrimary },
|
|
193
206
|
children: /* @__PURE__ */ s(n, {
|
|
194
207
|
dashboardId: f,
|
|
195
208
|
colors: G
|
|
196
209
|
})
|
|
197
210
|
}),
|
|
198
211
|
I === "people" && /* @__PURE__ */ c("div", {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
overflowY: "auto",
|
|
202
|
-
maxHeight: "calc(80vh - 200px)",
|
|
203
|
-
backgroundColor: G.bgPrimary
|
|
204
|
-
},
|
|
212
|
+
className: "flex min-h-0 flex-1 flex-col gap-section-gap overflow-y-auto p-card-padding",
|
|
213
|
+
style: { backgroundColor: G.bgPrimary },
|
|
205
214
|
children: [
|
|
206
|
-
/* @__PURE__ */ c("
|
|
207
|
-
|
|
208
|
-
children: [/* @__PURE__ */ s("
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
fontWeight: 600,
|
|
214
|
-
color: G.textPrimary
|
|
215
|
-
},
|
|
216
|
-
children: "Share Link"
|
|
215
|
+
/* @__PURE__ */ c("section", {
|
|
216
|
+
"aria-labelledby": "share-link-heading",
|
|
217
|
+
children: [/* @__PURE__ */ s("h3", {
|
|
218
|
+
id: "share-link-heading",
|
|
219
|
+
className: "mb-form-gap text-sm font-semibold",
|
|
220
|
+
style: { color: G.textPrimary },
|
|
221
|
+
children: "Share link"
|
|
217
222
|
}), /* @__PURE__ */ c("div", {
|
|
218
223
|
style: {
|
|
219
224
|
display: "flex",
|
|
220
|
-
gap: "8px"
|
|
225
|
+
gap: "8px",
|
|
226
|
+
flexWrap: "wrap"
|
|
221
227
|
},
|
|
222
228
|
children: [/* @__PURE__ */ s("input", {
|
|
223
229
|
type: "text",
|
|
@@ -237,7 +243,7 @@ function f({ dashboardId: f, dashboardName: m, isOpen: h, onClose: g }) {
|
|
|
237
243
|
style: {
|
|
238
244
|
padding: "10px 16px",
|
|
239
245
|
backgroundColor: P ? G.statusSuccessBg : G.actionPrimaryBg,
|
|
240
|
-
color: "
|
|
246
|
+
color: "var(--color-action-primary-text)",
|
|
241
247
|
border: "none",
|
|
242
248
|
borderRadius: "6px",
|
|
243
249
|
cursor: "pointer",
|
|
@@ -249,22 +255,19 @@ function f({ dashboardId: f, dashboardName: m, isOpen: h, onClose: g }) {
|
|
|
249
255
|
})]
|
|
250
256
|
})]
|
|
251
257
|
}),
|
|
252
|
-
/* @__PURE__ */ c("
|
|
253
|
-
|
|
254
|
-
children: [/* @__PURE__ */ s("
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
fontWeight: 600,
|
|
260
|
-
color: G.textPrimary
|
|
261
|
-
},
|
|
262
|
-
children: "Add People"
|
|
258
|
+
/* @__PURE__ */ c("section", {
|
|
259
|
+
"aria-labelledby": "share-invite-heading",
|
|
260
|
+
children: [/* @__PURE__ */ s("h3", {
|
|
261
|
+
id: "share-invite-heading",
|
|
262
|
+
className: "mb-form-gap text-sm font-semibold",
|
|
263
|
+
style: { color: G.textPrimary },
|
|
264
|
+
children: "Invite people"
|
|
263
265
|
}), /* @__PURE__ */ c("div", {
|
|
264
266
|
style: {
|
|
265
267
|
display: "flex",
|
|
266
268
|
gap: "8px",
|
|
267
|
-
marginBottom: "8px"
|
|
269
|
+
marginBottom: "8px",
|
|
270
|
+
flexWrap: "wrap"
|
|
268
271
|
},
|
|
269
272
|
children: [
|
|
270
273
|
/* @__PURE__ */ c("div", {
|
|
@@ -298,7 +301,7 @@ function f({ dashboardId: f, dashboardName: m, isOpen: h, onClose: g }) {
|
|
|
298
301
|
backgroundColor: G.bgPrimary,
|
|
299
302
|
border: `1px solid ${G.borderDefault}`,
|
|
300
303
|
borderRadius: "6px",
|
|
301
|
-
boxShadow: "
|
|
304
|
+
boxShadow: "var(--shadow-elevation-3)",
|
|
302
305
|
zIndex: 100001,
|
|
303
306
|
maxHeight: "200px",
|
|
304
307
|
overflowY: "auto"
|
|
@@ -365,7 +368,7 @@ function f({ dashboardId: f, dashboardName: m, isOpen: h, onClose: g }) {
|
|
|
365
368
|
style: {
|
|
366
369
|
padding: "10px 16px",
|
|
367
370
|
backgroundColor: O ? G.actionPrimaryBg : G.bgTertiary,
|
|
368
|
-
color: O ? "
|
|
371
|
+
color: O ? "var(--color-action-primary-text)" : G.textSecondary,
|
|
369
372
|
border: "none",
|
|
370
373
|
borderRadius: "6px",
|
|
371
374
|
cursor: O ? "pointer" : "not-allowed",
|
|
@@ -377,61 +380,64 @@ function f({ dashboardId: f, dashboardName: m, isOpen: h, onClose: g }) {
|
|
|
377
380
|
]
|
|
378
381
|
})]
|
|
379
382
|
}),
|
|
380
|
-
/* @__PURE__ */ c("
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
alignItems: "center",
|
|
384
|
-
justifyContent: "space-between",
|
|
385
|
-
marginBottom: "12px"
|
|
386
|
-
},
|
|
387
|
-
children: [/* @__PURE__ */ c("label", {
|
|
383
|
+
/* @__PURE__ */ c("section", {
|
|
384
|
+
"aria-labelledby": "share-access-heading",
|
|
385
|
+
children: [/* @__PURE__ */ c("div", {
|
|
388
386
|
style: {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
387
|
+
display: "flex",
|
|
388
|
+
alignItems: "center",
|
|
389
|
+
justifyContent: "space-between",
|
|
390
|
+
flexWrap: "wrap",
|
|
391
|
+
gap: "8px",
|
|
392
|
+
marginBottom: "12px"
|
|
392
393
|
},
|
|
393
|
-
children: [
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
394
|
+
children: [/* @__PURE__ */ c("h3", {
|
|
395
|
+
id: "share-access-heading",
|
|
396
|
+
className: "text-sm font-semibold",
|
|
397
|
+
style: { color: G.textPrimary },
|
|
398
|
+
children: [
|
|
399
|
+
"People with access (",
|
|
400
|
+
_.length,
|
|
401
|
+
")"
|
|
402
|
+
]
|
|
403
|
+
}), _.length > 0 && /* @__PURE__ */ s("button", {
|
|
404
|
+
onClick: V,
|
|
405
|
+
style: {
|
|
406
|
+
padding: "4px 8px",
|
|
407
|
+
backgroundColor: "transparent",
|
|
408
|
+
color: G.statusErrorText,
|
|
409
|
+
border: "none",
|
|
410
|
+
cursor: "pointer",
|
|
411
|
+
fontSize: "12px"
|
|
412
|
+
},
|
|
413
|
+
children: "Revoke All"
|
|
414
|
+
})]
|
|
415
|
+
}), v && _.length === 0 ? /* @__PURE__ */ s("p", {
|
|
416
|
+
style: {
|
|
417
|
+
color: G.textSecondary,
|
|
418
|
+
fontSize: "13px"
|
|
419
|
+
},
|
|
420
|
+
children: "Loading..."
|
|
421
|
+
}) : _.length === 0 ? /* @__PURE__ */ s("p", {
|
|
400
422
|
style: {
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
423
|
+
color: G.textSecondary,
|
|
424
|
+
fontSize: "13px"
|
|
425
|
+
},
|
|
426
|
+
children: "No one has access to this dashboard yet."
|
|
427
|
+
}) : /* @__PURE__ */ s("div", {
|
|
428
|
+
style: {
|
|
429
|
+
display: "flex",
|
|
430
|
+
flexDirection: "column",
|
|
431
|
+
gap: "8px"
|
|
407
432
|
},
|
|
408
|
-
children:
|
|
433
|
+
children: _.map((e) => /* @__PURE__ */ s(p, {
|
|
434
|
+
share: e,
|
|
435
|
+
onUpdatePermission: z,
|
|
436
|
+
onRevoke: B,
|
|
437
|
+
colors: G
|
|
438
|
+
}, e.id))
|
|
409
439
|
})]
|
|
410
|
-
})
|
|
411
|
-
style: {
|
|
412
|
-
color: G.textSecondary,
|
|
413
|
-
fontSize: "13px"
|
|
414
|
-
},
|
|
415
|
-
children: "Loading..."
|
|
416
|
-
}) : _.length === 0 ? /* @__PURE__ */ s("p", {
|
|
417
|
-
style: {
|
|
418
|
-
color: G.textSecondary,
|
|
419
|
-
fontSize: "13px"
|
|
420
|
-
},
|
|
421
|
-
children: "No one has access to this dashboard yet."
|
|
422
|
-
}) : /* @__PURE__ */ s("div", {
|
|
423
|
-
style: {
|
|
424
|
-
display: "flex",
|
|
425
|
-
flexDirection: "column",
|
|
426
|
-
gap: "8px"
|
|
427
|
-
},
|
|
428
|
-
children: _.map((e) => /* @__PURE__ */ s(p, {
|
|
429
|
-
share: e,
|
|
430
|
-
onUpdatePermission: z,
|
|
431
|
-
onRevoke: B,
|
|
432
|
-
colors: G
|
|
433
|
-
}, e.id))
|
|
434
|
-
})] })
|
|
440
|
+
})
|
|
435
441
|
]
|
|
436
442
|
})
|
|
437
443
|
]
|
|
@@ -463,7 +469,7 @@ function p({ share: e, onUpdatePermission: t, onRevoke: n, colors: r }) {
|
|
|
463
469
|
display: "flex",
|
|
464
470
|
alignItems: "center",
|
|
465
471
|
justifyContent: "center",
|
|
466
|
-
color: "
|
|
472
|
+
color: "var(--color-action-primary-text)",
|
|
467
473
|
fontWeight: 600,
|
|
468
474
|
fontSize: "14px"
|
|
469
475
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ShareDialog.js","names":[],"sources":["../../../../../src/bigconsole/components/dashboard/ShareDialog/ShareDialog.tsx"],"sourcesContent":["/**\n * ShareDialog Component\n *\n * Dialog for sharing dashboards with users and roles.\n */\n\nimport React, { useState, useEffect, useCallback, useMemo } from 'react';\nimport { createPortal } from 'react-dom';\nimport {\n useDashboardSharing,\n type DashboardShare,\n type SharePermission,\n type ShareRecipientType,\n} from '../../../hooks';\nimport { useBigConsoleAnalytics } from '../../../hooks/useBigConsoleAnalytics';\nimport { EmbedTab } from './EmbedTab';\n\n// Dark mode aware colors\nconst getColors = (isDark: boolean) => ({\n bgPrimary: isDark ? '#1a1a2e' : '#ffffff',\n bgSecondary: isDark ? '#252542' : '#f4f4f5',\n bgTertiary: isDark ? '#2d2d4a' : '#e4e4e7',\n textPrimary: isDark ? '#f4f4f5' : '#18181b',\n textSecondary: isDark ? '#a1a1aa' : '#71717a',\n textTertiary: isDark ? '#71717a' : '#a1a1aa',\n borderDefault: isDark ? '#3f3f5c' : '#e4e4e7',\n actionPrimaryBg: '#6366f1',\n actionPrimaryBgHover: '#4f46e5',\n statusSuccessBg: '#22c55e',\n statusErrorText: '#ef4444',\n});\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ShareDialogProps {\n dashboardId: string;\n dashboardName: string;\n isOpen: boolean;\n onClose: () => void;\n}\n\ninterface ShareRecipient {\n id: string;\n name: string;\n email?: string;\n type: ShareRecipientType;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/**\n * Permission options as per spec (03-graphql-contract.md)\n * Spec values: view, edit\n */\nconst PERMISSION_OPTIONS: { value: SharePermission; label: string; description: string }[] = [\n { value: 'VIEW', label: 'View', description: 'Can view the dashboard' },\n { value: 'EDIT', label: 'Edit', description: 'Can edit widgets, layout, and share' },\n];\n\n// Use shared semantic tokens for portal rendering\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport function ShareDialog({ dashboardId, dashboardName, isOpen, onClose }: ShareDialogProps) {\n const { shares, loading, fetchShares, shareWithUser, shareWithGroup, updateShare, revokeShare, revokeAllShares } =\n useDashboardSharing();\n const emitBigConsoleEvent = useBigConsoleAnalytics();\n\n const [searchQuery, setSearchQuery] = useState('');\n const [selectedRecipient, setSelectedRecipient] = useState<ShareRecipient | null>(null);\n const [selectedPermission, setSelectedPermission] = useState<SharePermission>('VIEW');\n const [shareLink, setShareLink] = useState('');\n const [copySuccess, setCopySuccess] = useState(false);\n const [activeTab, setActiveTab] = useState<'people' | 'embed'>('people');\n\n // Fetch shares when dialog opens\n useEffect(() => {\n if (isOpen && dashboardId) {\n fetchShares(dashboardId);\n // Generate share link\n setShareLink(`${window.location.origin}/dashboard/${dashboardId}?shared=true`);\n }\n }, [isOpen, dashboardId, fetchShares]);\n\n // Handle share - per spec: only USER and GROUP recipient types\n const handleShare = useCallback(async () => {\n if (!selectedRecipient) return;\n\n emitBigConsoleEvent('bigconsole.dashboard.share_clicked', {\n dashboardId,\n permission: selectedPermission,\n recipientType: selectedRecipient.type,\n });\n\n let share: DashboardShare | null = null;\n if (selectedRecipient.type === 'USER') {\n share = await shareWithUser(dashboardId, selectedRecipient.id, selectedPermission);\n } else if (selectedRecipient.type === 'GROUP') {\n share = await shareWithGroup(dashboardId, selectedRecipient.id, selectedPermission);\n }\n\n if (share) {\n emitBigConsoleEvent('bigconsole.dashboard.shared', {\n dashboardId,\n permission: selectedPermission,\n recipientType: selectedRecipient.type,\n });\n }\n\n setSelectedRecipient(null);\n setSearchQuery('');\n }, [dashboardId, emitBigConsoleEvent, selectedRecipient, selectedPermission, shareWithGroup, shareWithUser]);\n\n // Handle update permission\n const handleUpdatePermission = useCallback(\n async (shareId: string, newPermission: SharePermission) => {\n await updateShare({ id: shareId, permission: newPermission });\n },\n [updateShare]\n );\n\n // Handle revoke\n const handleRevoke = useCallback(\n async (shareId: string) => {\n if (window.confirm('Are you sure you want to revoke this access?')) {\n await revokeShare(shareId);\n }\n },\n [revokeShare]\n );\n\n // Handle revoke all\n const handleRevokeAll = useCallback(async () => {\n if (window.confirm('Are you sure you want to revoke all shared access?')) {\n await revokeAllShares(dashboardId);\n }\n }, [dashboardId, revokeAllShares]);\n\n // Copy link to clipboard\n const handleCopyLink = useCallback(async () => {\n try {\n await navigator.clipboard.writeText(shareLink);\n setCopySuccess(true);\n setTimeout(() => setCopySuccess(false), 2000);\n } catch (err) {}\n }, [shareLink]);\n\n // Mock search results (in real app, this would come from an API)\n // Per spec: ShareRecipientType is 'user' or 'group' only\n const searchResults: ShareRecipient[] = searchQuery\n ? (\n [\n { id: 'user-1', name: 'John Doe', email: 'john@example.com', type: 'USER' as ShareRecipientType },\n { id: 'user-2', name: 'Jane Smith', email: 'jane@example.com', type: 'USER' as ShareRecipientType },\n { id: 'group-admin', name: 'Administrators', type: 'GROUP' as ShareRecipientType },\n { id: 'group-editor', name: 'Editors', type: 'GROUP' as ShareRecipientType },\n ] as ShareRecipient[]\n ).filter(\n (r) =>\n r.name.toLowerCase().includes(searchQuery.toLowerCase()) ||\n r.email?.toLowerCase().includes(searchQuery.toLowerCase())\n )\n : [];\n\n // Check if dark mode is enabled\n const isDarkMode = typeof document !== 'undefined' && document.documentElement.classList.contains('dark');\n const colors = useMemo(() => getColors(isDarkMode), [isDarkMode]);\n\n if (!isOpen) return null;\n\n // Use portal to render at document.body level to avoid z-index/stacking context issues\n const dialogContent = (\n <div\n style={{\n position: 'fixed',\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n backgroundColor: 'rgba(0, 0, 0, 0.6)',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n zIndex: 99999,\n isolation: 'isolate',\n }}\n onClick={onClose}\n >\n <div\n style={{\n width: '520px',\n maxHeight: '80vh',\n backgroundColor: colors.bgPrimary,\n borderRadius: '12px',\n boxShadow: '0 8px 32px rgba(0, 0, 0, 0.3)',\n overflow: 'hidden',\n position: 'relative',\n zIndex: 100000,\n color: colors.textPrimary,\n }}\n onClick={(e) => e.stopPropagation()}\n >\n {/* Header */}\n <div\n style={{\n padding: '20px 24px',\n borderBottom: `1px solid ${colors.borderDefault}`,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n backgroundColor: colors.bgPrimary,\n }}\n >\n <div>\n <h3 style={{ margin: 0, fontSize: '18px', fontWeight: 600, color: colors.textPrimary }}>Share Dashboard</h3>\n <p style={{ margin: '4px 0 0', fontSize: '13px', color: colors.textSecondary }}>{dashboardName}</p>\n </div>\n <button\n onClick={onClose}\n style={{\n background: 'none',\n border: 'none',\n fontSize: '24px',\n cursor: 'pointer',\n color: colors.textSecondary,\n padding: '4px',\n lineHeight: 1,\n }}\n >\n ×\n </button>\n </div>\n\n {/* Tabs */}\n <div\n style={{\n display: 'flex',\n gap: '4px',\n padding: '0 24px',\n borderBottom: `1px solid ${colors.borderDefault}`,\n backgroundColor: colors.bgPrimary,\n }}\n >\n {(['people', 'embed'] as const).map((tab) => (\n <button\n key={tab}\n type=\"button\"\n onClick={() => setActiveTab(tab)}\n style={{\n padding: '10px 12px',\n background: 'none',\n border: 'none',\n borderBottom: `2px solid ${activeTab === tab ? colors.actionPrimaryBg : 'transparent'}`,\n color: activeTab === tab ? colors.textPrimary : colors.textSecondary,\n fontSize: '13px',\n fontWeight: 600,\n cursor: 'pointer',\n }}\n >\n {tab === 'people' ? 'People' : 'Publish to web'}\n </button>\n ))}\n </div>\n\n {/* Publish-to-web tab */}\n {activeTab === 'embed' && (\n <div style={{ overflowY: 'auto', maxHeight: 'calc(80vh - 160px)', backgroundColor: colors.bgPrimary }}>\n <EmbedTab dashboardId={dashboardId} colors={colors} />\n </div>\n )}\n\n {/* People tab (existing content) */}\n {activeTab === 'people' && (\n <div\n style={{\n padding: '24px',\n overflowY: 'auto',\n maxHeight: 'calc(80vh - 200px)',\n backgroundColor: colors.bgPrimary,\n }}\n >\n {/* Share Link Section */}\n <div style={{ marginBottom: '24px' }}>\n <label\n style={{\n display: 'block',\n marginBottom: '8px',\n fontSize: '13px',\n fontWeight: 600,\n color: colors.textPrimary,\n }}\n >\n Share Link\n </label>\n <div style={{ display: 'flex', gap: '8px' }}>\n <input\n type=\"text\"\n value={shareLink}\n readOnly\n style={{\n flex: 1,\n padding: '10px 12px',\n border: `1px solid ${colors.borderDefault}`,\n borderRadius: '6px',\n fontSize: '13px',\n backgroundColor: colors.bgSecondary,\n color: colors.textPrimary,\n }}\n />\n <button\n onClick={handleCopyLink}\n style={{\n padding: '10px 16px',\n backgroundColor: copySuccess ? colors.statusSuccessBg : colors.actionPrimaryBg,\n color: 'white',\n border: 'none',\n borderRadius: '6px',\n cursor: 'pointer',\n fontSize: '13px',\n fontWeight: 500,\n minWidth: '80px',\n }}\n >\n {copySuccess ? 'Copied!' : 'Copy'}\n </button>\n </div>\n </div>\n\n {/* Add People Section */}\n <div style={{ marginBottom: '24px' }}>\n <label\n style={{\n display: 'block',\n marginBottom: '8px',\n fontSize: '13px',\n fontWeight: 600,\n color: colors.textPrimary,\n }}\n >\n Add People\n </label>\n <div style={{ display: 'flex', gap: '8px', marginBottom: '8px' }}>\n <div style={{ flex: 1, position: 'relative' }}>\n <input\n type=\"text\"\n value={selectedRecipient ? selectedRecipient.name : searchQuery}\n onChange={(e) => {\n setSearchQuery(e.target.value);\n setSelectedRecipient(null);\n }}\n placeholder=\"Search by name or email...\"\n style={{\n width: '100%',\n padding: '10px 12px',\n border: `1px solid ${colors.borderDefault}`,\n borderRadius: '6px',\n fontSize: '13px',\n backgroundColor: colors.bgPrimary,\n color: colors.textPrimary,\n }}\n />\n {/* Search Results Dropdown */}\n {searchQuery && !selectedRecipient && searchResults.length > 0 && (\n <div\n style={{\n position: 'absolute',\n top: '100%',\n left: 0,\n right: 0,\n marginTop: '4px',\n backgroundColor: colors.bgPrimary,\n border: `1px solid ${colors.borderDefault}`,\n borderRadius: '6px',\n boxShadow: '0 8px 24px rgba(0,0,0,0.25)',\n zIndex: 100001,\n maxHeight: '200px',\n overflowY: 'auto',\n }}\n >\n {searchResults.map((result) => (\n <button\n key={result.id}\n onClick={() => setSelectedRecipient(result)}\n style={{\n display: 'block',\n width: '100%',\n padding: '10px 12px',\n textAlign: 'left',\n backgroundColor: 'transparent',\n border: 'none',\n cursor: 'pointer',\n borderBottom: `1px solid ${colors.borderDefault}`,\n }}\n >\n <div style={{ fontSize: '13px', fontWeight: 500, color: colors.textPrimary }}>\n {result.name}\n </div>\n {result.email && (\n <div style={{ fontSize: '11px', color: colors.textSecondary }}>{result.email}</div>\n )}\n <span\n style={{\n fontSize: '10px',\n color: colors.textTertiary,\n textTransform: 'uppercase',\n }}\n >\n {result.type}\n </span>\n </button>\n ))}\n </div>\n )}\n </div>\n <select\n value={selectedPermission}\n onChange={(e) => setSelectedPermission(e.target.value as SharePermission)}\n style={{\n padding: '10px 12px',\n border: `1px solid ${colors.borderDefault}`,\n borderRadius: '6px',\n fontSize: '13px',\n backgroundColor: colors.bgPrimary,\n color: colors.textPrimary,\n }}\n >\n {PERMISSION_OPTIONS.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n <button\n onClick={handleShare}\n disabled={!selectedRecipient || loading}\n style={{\n padding: '10px 16px',\n backgroundColor: selectedRecipient ? colors.actionPrimaryBg : colors.bgTertiary,\n color: selectedRecipient ? 'white' : colors.textSecondary,\n border: 'none',\n borderRadius: '6px',\n cursor: selectedRecipient ? 'pointer' : 'not-allowed',\n fontSize: '13px',\n fontWeight: 500,\n }}\n >\n Share\n </button>\n </div>\n </div>\n\n {/* Current Shares */}\n <div>\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n marginBottom: '12px',\n }}\n >\n <label\n style={{\n fontSize: '13px',\n fontWeight: 600,\n color: colors.textPrimary,\n }}\n >\n People with Access ({shares.length})\n </label>\n {shares.length > 0 && (\n <button\n onClick={handleRevokeAll}\n style={{\n padding: '4px 8px',\n backgroundColor: 'transparent',\n color: colors.statusErrorText,\n border: 'none',\n cursor: 'pointer',\n fontSize: '12px',\n }}\n >\n Revoke All\n </button>\n )}\n </div>\n\n {loading && shares.length === 0 ? (\n <p style={{ color: colors.textSecondary, fontSize: '13px' }}>Loading...</p>\n ) : shares.length === 0 ? (\n <p style={{ color: colors.textSecondary, fontSize: '13px' }}>\n No one has access to this dashboard yet.\n </p>\n ) : (\n <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>\n {shares.map((share) => (\n <ShareItem\n key={share.id}\n share={share}\n onUpdatePermission={handleUpdatePermission}\n onRevoke={handleRevoke}\n colors={colors}\n />\n ))}\n </div>\n )}\n </div>\n </div>\n )}\n </div>\n </div>\n );\n\n // Render via portal to document.body to escape any stacking context issues\n return createPortal(dialogContent, document.body);\n}\n\n// ============================================================================\n// ShareItem Sub-component\n// ============================================================================\n\ninterface ShareItemProps {\n share: DashboardShare;\n onUpdatePermission: (shareId: string, permission: SharePermission) => void;\n onRevoke: (shareId: string) => void;\n colors: ReturnType<typeof getColors>;\n}\n\nfunction ShareItem({ share, onUpdatePermission, onRevoke, colors }: ShareItemProps) {\n return (\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n padding: '10px 12px',\n backgroundColor: colors.bgSecondary,\n borderRadius: '6px',\n }}\n >\n <div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>\n <div\n style={{\n width: '36px',\n height: '36px',\n borderRadius: '50%',\n backgroundColor: colors.actionPrimaryBg,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n color: 'white',\n fontWeight: 600,\n fontSize: '14px',\n }}\n >\n {share.recipientType === 'USER' ? 'U' : 'R'}\n </div>\n <div>\n <div style={{ fontSize: '13px', fontWeight: 500, color: colors.textPrimary }}>{share.recipientId}</div>\n <div style={{ fontSize: '11px', color: colors.textSecondary }}>\n {share.recipientType} - Shared {new Date(share.createdAt).toLocaleDateString()}\n </div>\n </div>\n </div>\n <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>\n <select\n value={share.permission}\n onChange={(e) => onUpdatePermission(share.id, e.target.value as SharePermission)}\n style={{\n padding: '6px 10px',\n border: `1px solid ${colors.borderDefault}`,\n borderRadius: '4px',\n fontSize: '12px',\n backgroundColor: colors.bgPrimary,\n color: colors.textPrimary,\n }}\n >\n {PERMISSION_OPTIONS.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n <button\n onClick={() => onRevoke(share.id)}\n style={{\n padding: '6px 10px',\n backgroundColor: 'transparent',\n color: colors.statusErrorText,\n border: `1px solid ${colors.statusErrorText}`,\n borderRadius: '4px',\n cursor: 'pointer',\n fontSize: '12px',\n }}\n >\n Revoke\n </button>\n </div>\n </div>\n );\n}\n\nexport default ShareDialog;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ShareDialog.js","names":[],"sources":["../../../../../src/bigconsole/components/dashboard/ShareDialog/ShareDialog.tsx"],"sourcesContent":["/**\n * ShareDialog Component\n *\n * Dialog for sharing dashboards with users and roles.\n */\n\nimport React, { useState, useEffect, useCallback, useMemo } from 'react';\nimport { createPortal } from 'react-dom';\nimport {\n useDashboardSharing,\n type DashboardShare,\n type SharePermission,\n type ShareRecipientType,\n} from '../../../hooks';\nimport { useBigConsoleAnalytics } from '../../../hooks/useBigConsoleAnalytics';\nimport { EmbedTab } from './EmbedTab';\n\n// Dark mode aware colors\nconst getColors = (isDark: boolean) => ({\n bgPrimary: isDark ? '#1a1a2e' : '#ffffff',\n bgSecondary: isDark ? '#252542' : '#f4f4f5',\n bgTertiary: isDark ? '#2d2d4a' : '#e4e4e7',\n textPrimary: isDark ? '#f4f4f5' : '#18181b',\n textSecondary: isDark ? '#a1a1aa' : '#71717a',\n textTertiary: isDark ? '#71717a' : '#a1a1aa',\n borderDefault: isDark ? '#3f3f5c' : '#e4e4e7',\n actionPrimaryBg: '#6366f1',\n actionPrimaryBgHover: '#4f46e5',\n statusSuccessBg: '#22c55e',\n statusErrorText: '#ef4444',\n});\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ShareDialogProps {\n dashboardId: string;\n dashboardName: string;\n isOpen: boolean;\n onClose: () => void;\n}\n\ninterface ShareRecipient {\n id: string;\n name: string;\n email?: string;\n type: ShareRecipientType;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/**\n * Permission options as per spec (03-graphql-contract.md)\n * Spec values: view, edit\n */\nconst PERMISSION_OPTIONS: { value: SharePermission; label: string; description: string }[] = [\n { value: 'VIEW', label: 'View', description: 'Can view the dashboard' },\n { value: 'EDIT', label: 'Edit', description: 'Can edit widgets, layout, and share' },\n];\n\n// Use shared semantic tokens for portal rendering\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport function ShareDialog({ dashboardId, dashboardName, isOpen, onClose }: ShareDialogProps) {\n const { shares, loading, fetchShares, shareWithUser, shareWithGroup, updateShare, revokeShare, revokeAllShares } =\n useDashboardSharing();\n const emitBigConsoleEvent = useBigConsoleAnalytics();\n\n const [searchQuery, setSearchQuery] = useState('');\n const [selectedRecipient, setSelectedRecipient] = useState<ShareRecipient | null>(null);\n const [selectedPermission, setSelectedPermission] = useState<SharePermission>('VIEW');\n const [shareLink, setShareLink] = useState('');\n const [copySuccess, setCopySuccess] = useState(false);\n const [activeTab, setActiveTab] = useState<'people' | 'embed'>('people');\n\n // Fetch shares when dialog opens\n useEffect(() => {\n if (isOpen && dashboardId) {\n fetchShares(dashboardId);\n // Generate share link\n setShareLink(`${window.location.origin}/dashboard/${dashboardId}?shared=true`);\n }\n }, [isOpen, dashboardId, fetchShares]);\n\n // Handle share - per spec: only USER and GROUP recipient types\n const handleShare = useCallback(async () => {\n if (!selectedRecipient) return;\n\n emitBigConsoleEvent('bigconsole.dashboard.share_clicked', {\n dashboardId,\n permission: selectedPermission,\n recipientType: selectedRecipient.type,\n });\n\n let share: DashboardShare | null = null;\n if (selectedRecipient.type === 'USER') {\n share = await shareWithUser(dashboardId, selectedRecipient.id, selectedPermission);\n } else if (selectedRecipient.type === 'GROUP') {\n share = await shareWithGroup(dashboardId, selectedRecipient.id, selectedPermission);\n }\n\n if (share) {\n emitBigConsoleEvent('bigconsole.dashboard.shared', {\n dashboardId,\n permission: selectedPermission,\n recipientType: selectedRecipient.type,\n });\n }\n\n setSelectedRecipient(null);\n setSearchQuery('');\n }, [dashboardId, emitBigConsoleEvent, selectedRecipient, selectedPermission, shareWithGroup, shareWithUser]);\n\n // Handle update permission\n const handleUpdatePermission = useCallback(\n async (shareId: string, newPermission: SharePermission) => {\n await updateShare({ id: shareId, permission: newPermission });\n },\n [updateShare]\n );\n\n // Handle revoke\n const handleRevoke = useCallback(\n async (shareId: string) => {\n if (window.confirm('Are you sure you want to revoke this access?')) {\n await revokeShare(shareId);\n }\n },\n [revokeShare]\n );\n\n // Handle revoke all\n const handleRevokeAll = useCallback(async () => {\n if (window.confirm('Are you sure you want to revoke all shared access?')) {\n await revokeAllShares(dashboardId);\n }\n }, [dashboardId, revokeAllShares]);\n\n // Copy link to clipboard\n const handleCopyLink = useCallback(async () => {\n try {\n await navigator.clipboard.writeText(shareLink);\n setCopySuccess(true);\n setTimeout(() => setCopySuccess(false), 2000);\n } catch (err) {}\n }, [shareLink]);\n\n // Mock search results (in real app, this would come from an API)\n // Per spec: ShareRecipientType is 'user' or 'group' only\n const searchResults: ShareRecipient[] = searchQuery\n ? (\n [\n { id: 'user-1', name: 'John Doe', email: 'john@example.com', type: 'USER' as ShareRecipientType },\n { id: 'user-2', name: 'Jane Smith', email: 'jane@example.com', type: 'USER' as ShareRecipientType },\n { id: 'group-admin', name: 'Administrators', type: 'GROUP' as ShareRecipientType },\n { id: 'group-editor', name: 'Editors', type: 'GROUP' as ShareRecipientType },\n ] as ShareRecipient[]\n ).filter(\n (r) =>\n r.name.toLowerCase().includes(searchQuery.toLowerCase()) ||\n r.email?.toLowerCase().includes(searchQuery.toLowerCase())\n )\n : [];\n\n // Check if dark mode is enabled\n const isDarkMode = typeof document !== 'undefined' && document.documentElement.classList.contains('dark');\n const colors = useMemo(() => getColors(isDarkMode), [isDarkMode]);\n\n if (!isOpen) return null;\n\n // Use portal to render at document.body level to avoid z-index/stacking context issues\n const dialogContent = (\n <div\n style={{\n position: 'fixed',\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n backgroundColor: 'var(--color-overlay-scrim)',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n padding: 'var(--space-formGap)',\n zIndex: 99999,\n isolation: 'isolate',\n }}\n onClick={onClose}\n role=\"presentation\"\n >\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"share-dialog-title\"\n data-testid=\"share-dialog\"\n style={{\n // was a hard 520px, which overflowed every phone viewport\n width: '100%',\n maxWidth: '520px',\n maxHeight: '85vh',\n display: 'flex',\n flexDirection: 'column',\n backgroundColor: colors.bgPrimary,\n borderRadius: 'var(--radius-card)',\n boxShadow: 'var(--shadow-elevation-3)',\n overflow: 'hidden',\n position: 'relative',\n zIndex: 100000,\n color: colors.textPrimary,\n }}\n onClick={(e) => e.stopPropagation()}\n >\n {/* Header */}\n <div\n style={{\n flexShrink: 0,\n padding: '20px 24px',\n borderBottom: `1px solid ${colors.borderDefault}`,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n backgroundColor: colors.bgPrimary,\n }}\n >\n <div>\n <h2\n id=\"share-dialog-title\"\n style={{ margin: 0, fontSize: '18px', fontWeight: 600, color: colors.textPrimary }}\n >\n Share Dashboard\n </h2>\n <p style={{ margin: '4px 0 0', fontSize: '13px', color: colors.textSecondary }}>{dashboardName}</p>\n </div>\n <button\n onClick={onClose}\n style={{\n background: 'none',\n border: 'none',\n fontSize: '24px',\n cursor: 'pointer',\n color: colors.textSecondary,\n padding: '4px',\n lineHeight: 1,\n }}\n >\n ×\n </button>\n </div>\n\n {/* Tabs */}\n <div\n role=\"tablist\"\n aria-label=\"Share options\"\n style={{\n flexShrink: 0,\n display: 'flex',\n gap: '4px',\n padding: '0 24px',\n borderBottom: `1px solid ${colors.borderDefault}`,\n backgroundColor: colors.bgPrimary,\n }}\n >\n {(['people', 'embed'] as const).map((tab) => (\n <button\n key={tab}\n type=\"button\"\n role=\"tab\"\n aria-selected={activeTab === tab}\n onClick={() => setActiveTab(tab)}\n style={{\n padding: '10px 12px',\n background: 'none',\n border: 'none',\n borderBottom: `2px solid ${activeTab === tab ? colors.actionPrimaryBg : 'transparent'}`,\n color: activeTab === tab ? colors.textPrimary : colors.textSecondary,\n fontSize: '13px',\n fontWeight: 600,\n cursor: 'pointer',\n }}\n >\n {tab === 'people' ? 'People' : 'Publish to web'}\n </button>\n ))}\n </div>\n\n {/* Publish-to-web tab */}\n {activeTab === 'embed' && (\n <div className=\"min-h-0 flex-1 overflow-y-auto\" style={{ backgroundColor: colors.bgPrimary }}>\n <EmbedTab dashboardId={dashboardId} colors={colors} />\n </div>\n )}\n\n {/* People tab (existing content) */}\n {activeTab === 'people' && (\n <div\n className=\"flex min-h-0 flex-1 flex-col gap-section-gap overflow-y-auto p-card-padding\"\n style={{ backgroundColor: colors.bgPrimary }}\n >\n {/* Share Link Section — the fastest way to share, so it leads */}\n <section aria-labelledby=\"share-link-heading\">\n <h3\n id=\"share-link-heading\"\n className=\"mb-form-gap text-sm font-semibold\"\n style={{ color: colors.textPrimary }}\n >\n Share link\n </h3>\n <div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>\n <input\n type=\"text\"\n value={shareLink}\n readOnly\n style={{\n flex: 1,\n padding: '10px 12px',\n border: `1px solid ${colors.borderDefault}`,\n borderRadius: '6px',\n fontSize: '13px',\n backgroundColor: colors.bgSecondary,\n color: colors.textPrimary,\n }}\n />\n <button\n onClick={handleCopyLink}\n style={{\n padding: '10px 16px',\n backgroundColor: copySuccess ? colors.statusSuccessBg : colors.actionPrimaryBg,\n color: 'var(--color-action-primary-text)',\n border: 'none',\n borderRadius: '6px',\n cursor: 'pointer',\n fontSize: '13px',\n fontWeight: 500,\n minWidth: '80px',\n }}\n >\n {copySuccess ? 'Copied!' : 'Copy'}\n </button>\n </div>\n </section>\n\n {/* Add People Section */}\n <section aria-labelledby=\"share-invite-heading\">\n <h3\n id=\"share-invite-heading\"\n className=\"mb-form-gap text-sm font-semibold\"\n style={{ color: colors.textPrimary }}\n >\n Invite people\n </h3>\n <div style={{ display: 'flex', gap: '8px', marginBottom: '8px', flexWrap: 'wrap' }}>\n <div style={{ flex: 1, position: 'relative' }}>\n <input\n type=\"text\"\n value={selectedRecipient ? selectedRecipient.name : searchQuery}\n onChange={(e) => {\n setSearchQuery(e.target.value);\n setSelectedRecipient(null);\n }}\n placeholder=\"Search by name or email...\"\n style={{\n width: '100%',\n padding: '10px 12px',\n border: `1px solid ${colors.borderDefault}`,\n borderRadius: '6px',\n fontSize: '13px',\n backgroundColor: colors.bgPrimary,\n color: colors.textPrimary,\n }}\n />\n {/* Search Results Dropdown */}\n {searchQuery && !selectedRecipient && searchResults.length > 0 && (\n <div\n style={{\n position: 'absolute',\n top: '100%',\n left: 0,\n right: 0,\n marginTop: '4px',\n backgroundColor: colors.bgPrimary,\n border: `1px solid ${colors.borderDefault}`,\n borderRadius: '6px',\n boxShadow: 'var(--shadow-elevation-3)',\n zIndex: 100001,\n maxHeight: '200px',\n overflowY: 'auto',\n }}\n >\n {searchResults.map((result) => (\n <button\n key={result.id}\n onClick={() => setSelectedRecipient(result)}\n style={{\n display: 'block',\n width: '100%',\n padding: '10px 12px',\n textAlign: 'left',\n backgroundColor: 'transparent',\n border: 'none',\n cursor: 'pointer',\n borderBottom: `1px solid ${colors.borderDefault}`,\n }}\n >\n <div style={{ fontSize: '13px', fontWeight: 500, color: colors.textPrimary }}>\n {result.name}\n </div>\n {result.email && (\n <div style={{ fontSize: '11px', color: colors.textSecondary }}>{result.email}</div>\n )}\n <span\n style={{\n fontSize: '10px',\n color: colors.textTertiary,\n textTransform: 'uppercase',\n }}\n >\n {result.type}\n </span>\n </button>\n ))}\n </div>\n )}\n </div>\n <select\n value={selectedPermission}\n onChange={(e) => setSelectedPermission(e.target.value as SharePermission)}\n style={{\n padding: '10px 12px',\n border: `1px solid ${colors.borderDefault}`,\n borderRadius: '6px',\n fontSize: '13px',\n backgroundColor: colors.bgPrimary,\n color: colors.textPrimary,\n }}\n >\n {PERMISSION_OPTIONS.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n <button\n onClick={handleShare}\n disabled={!selectedRecipient || loading}\n style={{\n padding: '10px 16px',\n backgroundColor: selectedRecipient ? colors.actionPrimaryBg : colors.bgTertiary,\n color: selectedRecipient ? 'var(--color-action-primary-text)' : colors.textSecondary,\n border: 'none',\n borderRadius: '6px',\n cursor: selectedRecipient ? 'pointer' : 'not-allowed',\n fontSize: '13px',\n fontWeight: 500,\n }}\n >\n Share\n </button>\n </div>\n </section>\n\n {/* Current Shares */}\n <section aria-labelledby=\"share-access-heading\">\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n flexWrap: 'wrap',\n gap: '8px',\n marginBottom: '12px',\n }}\n >\n <h3 id=\"share-access-heading\" className=\"text-sm font-semibold\" style={{ color: colors.textPrimary }}>\n People with access ({shares.length})\n </h3>\n {shares.length > 0 && (\n <button\n onClick={handleRevokeAll}\n style={{\n padding: '4px 8px',\n backgroundColor: 'transparent',\n color: colors.statusErrorText,\n border: 'none',\n cursor: 'pointer',\n fontSize: '12px',\n }}\n >\n Revoke All\n </button>\n )}\n </div>\n\n {loading && shares.length === 0 ? (\n <p style={{ color: colors.textSecondary, fontSize: '13px' }}>Loading...</p>\n ) : shares.length === 0 ? (\n <p style={{ color: colors.textSecondary, fontSize: '13px' }}>\n No one has access to this dashboard yet.\n </p>\n ) : (\n <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>\n {shares.map((share) => (\n <ShareItem\n key={share.id}\n share={share}\n onUpdatePermission={handleUpdatePermission}\n onRevoke={handleRevoke}\n colors={colors}\n />\n ))}\n </div>\n )}\n </section>\n </div>\n )}\n </div>\n </div>\n );\n\n // Render via portal to document.body to escape any stacking context issues\n return createPortal(dialogContent, document.body);\n}\n\n// ============================================================================\n// ShareItem Sub-component\n// ============================================================================\n\ninterface ShareItemProps {\n share: DashboardShare;\n onUpdatePermission: (shareId: string, permission: SharePermission) => void;\n onRevoke: (shareId: string) => void;\n colors: ReturnType<typeof getColors>;\n}\n\nfunction ShareItem({ share, onUpdatePermission, onRevoke, colors }: ShareItemProps) {\n return (\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n padding: '10px 12px',\n backgroundColor: colors.bgSecondary,\n borderRadius: '6px',\n }}\n >\n <div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>\n <div\n style={{\n width: '36px',\n height: '36px',\n borderRadius: '50%',\n backgroundColor: colors.actionPrimaryBg,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n color: 'var(--color-action-primary-text)',\n fontWeight: 600,\n fontSize: '14px',\n }}\n >\n {share.recipientType === 'USER' ? 'U' : 'R'}\n </div>\n <div>\n <div style={{ fontSize: '13px', fontWeight: 500, color: colors.textPrimary }}>{share.recipientId}</div>\n <div style={{ fontSize: '11px', color: colors.textSecondary }}>\n {share.recipientType} - Shared {new Date(share.createdAt).toLocaleDateString()}\n </div>\n </div>\n </div>\n <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>\n <select\n value={share.permission}\n onChange={(e) => onUpdatePermission(share.id, e.target.value as SharePermission)}\n style={{\n padding: '6px 10px',\n border: `1px solid ${colors.borderDefault}`,\n borderRadius: '4px',\n fontSize: '12px',\n backgroundColor: colors.bgPrimary,\n color: colors.textPrimary,\n }}\n >\n {PERMISSION_OPTIONS.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n <button\n onClick={() => onRevoke(share.id)}\n style={{\n padding: '6px 10px',\n backgroundColor: 'transparent',\n color: colors.statusErrorText,\n border: `1px solid ${colors.statusErrorText}`,\n borderRadius: '4px',\n cursor: 'pointer',\n fontSize: '12px',\n }}\n >\n Revoke\n </button>\n </div>\n </div>\n );\n}\n\nexport default ShareDialog;\n"],"mappings":"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"}
|