@burdenoff/microfe-bigconsole 2026.812.2 → 2026.827.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/builder/inspector/LayoutSection.js +71 -0
- package/dist/bigconsole/components/builder/inspector/LayoutSection.js.map +1 -0
- package/dist/bigconsole/components/builder/inspector/index.js +1 -0
- package/dist/bigconsole/components/dashboard/DashboardCanvas.js +137 -121
- package/dist/bigconsole/components/dashboard/DashboardCanvas.js.map +1 -1
- package/dist/bigconsole/components/dashboard/ExportDashboardDialog.js +204 -93
- package/dist/bigconsole/components/dashboard/ExportDashboardDialog.js.map +1 -1
- package/dist/bigconsole/components/dashboard/ShareDialog/ShareDialog.js +259 -133
- package/dist/bigconsole/components/dashboard/ShareDialog/ShareDialog.js.map +1 -1
- package/dist/bigconsole/components/dashboard/StoreTemplatePicker.js +87 -39
- package/dist/bigconsole/components/dashboard/StoreTemplatePicker.js.map +1 -1
- package/dist/bigconsole/components/datasink/DataSinkCard.js +88 -68
- package/dist/bigconsole/components/datasink/DataSinkCard.js.map +1 -1
- package/dist/bigconsole/components/widgets/PropertiesPanel.js +213 -199
- package/dist/bigconsole/components/widgets/PropertiesPanel.js.map +1 -1
- package/dist/bigconsole/components/workflow/WorkflowCard.js +46 -27
- package/dist/bigconsole/components/workflow/WorkflowCard.js.map +1 -1
- package/dist/bigconsole/customWidget/CustomWidgetBuilderSheet.js +44 -47
- package/dist/bigconsole/customWidget/CustomWidgetBuilderSheet.js.map +1 -1
- package/dist/bigconsole/customWidget/proposalSource.js +31 -24
- package/dist/bigconsole/customWidget/proposalSource.js.map +1 -1
- package/dist/bigconsole/hooks/useDashboardOperations.js +74 -64
- package/dist/bigconsole/hooks/useDashboardOperations.js.map +1 -1
- package/dist/bigconsole/hooks/useDataSinkOperations.js +2 -2
- package/dist/bigconsole/hooks/useDataSinkOperations.js.map +1 -1
- package/dist/bigconsole/pages/DashboardBuilderPage.js +1 -1
- package/dist/bigconsole/pages/DashboardsPage.js +386 -316
- package/dist/bigconsole/pages/DashboardsPage.js.map +1 -1
- package/dist/bigconsole/pages/DataSinksPage.js +251 -206
- package/dist/bigconsole/pages/DataSinksPage.js.map +1 -1
- package/dist/bigconsole/pages/InsightsPage.js +646 -378
- package/dist/bigconsole/pages/InsightsPage.js.map +1 -1
- package/dist/bigconsole/pages/WorkflowsPage.js +153 -108
- package/dist/bigconsole/pages/WorkflowsPage.js.map +1 -1
- package/dist/bigconsole/utils/format.js +18 -0
- package/dist/bigconsole/utils/format.js.map +1 -0
- package/dist/bigconsole/utils/widgetPositioning.js +1 -1
- package/dist/generated/wspace-operations.js +1 -0
- package/dist/generated/wspace-operations.js.map +1 -1
- package/package.json +1 -1
|
@@ -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: '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":";;;;;;;;AAkBA,IAAM,KAAa,OAAqB;CACtC,WAAW,IAAS,YAAY;CAChC,aAAa,IAAS,YAAY;CAClC,YAAY,IAAS,YAAY;CACjC,aAAa,IAAS,YAAY;CAClC,eAAe,IAAS,YAAY;CACpC,cAAc,IAAS,YAAY;CACnC,eAAe,IAAS,YAAY;CACpC,iBAAiB;CACjB,sBAAsB;CACtB,iBAAiB;CACjB,iBAAiB;CAClB,GA4BK,IAAuF,CAC3F;CAAE,OAAO;CAAQ,OAAO;CAAQ,aAAa;CAA0B,EACvE;CAAE,OAAO;CAAQ,OAAO;CAAQ,aAAa;CAAuC,CACrF;AAQD,SAAgB,EAAY,EAAE,gBAAa,kBAAe,WAAQ,cAA6B;CAC7F,IAAM,EAAE,WAAQ,YAAS,gBAAa,kBAAe,mBAAgB,gBAAa,gBAAa,uBAC7F,GAAqB,EACjB,IAAsB,GAAwB,EAE9C,CAAC,GAAa,KAAkB,EAAS,GAAG,EAC5C,CAAC,GAAmB,KAAwB,EAAgC,KAAK,EACjF,CAAC,GAAoB,KAAyB,EAA0B,OAAO,EAC/E,CAAC,GAAW,KAAgB,EAAS,GAAG,EACxC,CAAC,GAAa,KAAkB,EAAS,GAAM,EAC/C,CAAC,GAAW,KAAgB,EAA6B,SAAS;AAGxE,SAAgB;AACd,EAAI,KAAU,MACZ,EAAY,EAAY,EAExB,EAAa,GAAG,OAAO,SAAS,OAAO,aAAa,EAAY,cAAc;IAE/E;EAAC;EAAQ;EAAa;EAAY,CAAC;CAGtC,IAAM,IAAc,EAAY,YAAY;AAC1C,MAAI,CAAC,EAAmB;AAExB,IAAoB,sCAAsC;GACxD;GACA,YAAY;GACZ,eAAe,EAAkB;GAClC,CAAC;EAEF,IAAI,IAA+B;AAgBnC,EAfI,EAAkB,SAAS,SAC7B,IAAQ,MAAM,EAAc,GAAa,EAAkB,IAAI,EAAmB,GACzE,EAAkB,SAAS,YACpC,IAAQ,MAAM,EAAe,GAAa,EAAkB,IAAI,EAAmB,GAGjF,KACF,EAAoB,+BAA+B;GACjD;GACA,YAAY;GACZ,eAAe,EAAkB;GAClC,CAAC,EAGJ,EAAqB,KAAK,EAC1B,EAAe,GAAG;IACjB;EAAC;EAAa;EAAqB;EAAmB;EAAoB;EAAgB;EAAc,CAAC,EAGtG,IAAyB,EAC7B,OAAO,GAAiB,MAAmC;AACzD,QAAM,EAAY;GAAE,IAAI;GAAS,YAAY;GAAe,CAAC;IAE/D,CAAC,EAAY,CACd,EAGK,IAAe,EACnB,OAAO,MAAoB;AACzB,EAAI,OAAO,QAAQ,+CAA+C,IAChE,MAAM,EAAY,EAAQ;IAG9B,CAAC,EAAY,CACd,EAGK,IAAkB,EAAY,YAAY;AAC9C,EAAI,OAAO,QAAQ,qDAAqD,IACtE,MAAM,EAAgB,EAAY;IAEnC,CAAC,GAAa,EAAgB,CAAC,EAG5B,IAAiB,EAAY,YAAY;AAC7C,MAAI;AAGF,GAFA,MAAM,UAAU,UAAU,UAAU,EAAU,EAC9C,EAAe,GAAK,EACpB,iBAAiB,EAAe,GAAM,EAAE,IAAK;UACjC;IACb,CAAC,EAAU,CAAC,EAIT,IAAkC,IAElC;EACE;GAAE,IAAI;GAAU,MAAM;GAAY,OAAO;GAAoB,MAAM;GAA8B;EACjG;GAAE,IAAI;GAAU,MAAM;GAAc,OAAO;GAAoB,MAAM;GAA8B;EACnG;GAAE,IAAI;GAAe,MAAM;GAAkB,MAAM;GAA+B;EAClF;GAAE,IAAI;GAAgB,MAAM;GAAW,MAAM;GAA+B;EAC7E,CACD,QACC,MACC,EAAE,KAAK,aAAa,CAAC,SAAS,EAAY,aAAa,CAAC,IACxD,EAAE,OAAO,aAAa,CAAC,SAAS,EAAY,aAAa,CAAC,CAC7D,GACD,EAAE,EAGA,IAAa,OAAO,WAAa,OAAe,SAAS,gBAAgB,UAAU,SAAS,OAAO,EACnG,IAAS,QAAc,EAAU,EAAW,EAAE,CAAC,EAAW,CAAC;AAiWjE,QA/VK,IA+VE,EA3VL,kBAAC,OAAD;EACE,OAAO;GACL,UAAU;GACV,KAAK;GACL,MAAM;GACN,OAAO;GACP,QAAQ;GACR,iBAAiB;GACjB,SAAS;GACT,YAAY;GACZ,gBAAgB;GAChB,SAAS;GACT,QAAQ;GACR,WAAW;GACZ;EACD,SAAS;EACT,MAAK;YAEL,kBAAC,OAAD;GACE,MAAK;GACL,cAAW;GACX,mBAAgB;GAChB,eAAY;GACZ,OAAO;IAEL,OAAO;IACP,UAAU;IACV,WAAW;IACX,SAAS;IACT,eAAe;IACf,iBAAiB,EAAO;IACxB,cAAc;IACd,WAAW;IACX,UAAU;IACV,UAAU;IACV,QAAQ;IACR,OAAO,EAAO;IACf;GACD,UAAU,MAAM,EAAE,iBAAiB;aApBrC;IAuBE,kBAAC,OAAD;KACE,OAAO;MACL,YAAY;MACZ,SAAS;MACT,cAAc,aAAa,EAAO;MAClC,SAAS;MACT,YAAY;MACZ,gBAAgB;MAChB,iBAAiB,EAAO;MACzB;eATH,CAWE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;MACE,IAAG;MACH,OAAO;OAAE,QAAQ;OAAG,UAAU;OAAQ,YAAY;OAAK,OAAO,EAAO;OAAa;gBACnF;MAEI,CAAA,EACL,kBAAC,KAAD;MAAG,OAAO;OAAE,QAAQ;OAAW,UAAU;OAAQ,OAAO,EAAO;OAAe;gBAAG;MAAkB,CAAA,CAC/F,EAAA,CAAA,EACN,kBAAC,UAAD;MACE,SAAS;MACT,OAAO;OACL,YAAY;OACZ,QAAQ;OACR,UAAU;OACV,QAAQ;OACR,OAAO,EAAO;OACd,SAAS;OACT,YAAY;OACb;gBACF;MAEQ,CAAA,CACL;;IAGN,kBAAC,OAAD;KACE,MAAK;KACL,cAAW;KACX,OAAO;MACL,YAAY;MACZ,SAAS;MACT,KAAK;MACL,SAAS;MACT,cAAc,aAAa,EAAO;MAClC,iBAAiB,EAAO;MACzB;eAEC,CAAC,UAAU,QAAQ,CAAW,KAAK,MACnC,kBAAC,UAAD;MAEE,MAAK;MACL,MAAK;MACL,iBAAe,MAAc;MAC7B,eAAe,EAAa,EAAI;MAChC,OAAO;OACL,SAAS;OACT,YAAY;OACZ,QAAQ;OACR,cAAc,aAAa,MAAc,IAAM,EAAO,kBAAkB;OACxE,OAAO,MAAc,IAAM,EAAO,cAAc,EAAO;OACvD,UAAU;OACV,YAAY;OACZ,QAAQ;OACT;gBAEA,MAAQ,WAAW,WAAW;MACxB,EAjBF,EAiBE,CACT;KACE,CAAA;IAGL,MAAc,WACb,kBAAC,OAAD;KAAK,WAAU;KAAiC,OAAO,EAAE,iBAAiB,EAAO,WAAW;eAC1F,kBAAC,GAAD;MAAuB;MAAqB;MAAU,CAAA;KAClD,CAAA;IAIP,MAAc,YACb,kBAAC,OAAD;KACE,WAAU;KACV,OAAO,EAAE,iBAAiB,EAAO,WAAW;eAF9C;MAKE,kBAAC,WAAD;OAAS,mBAAgB;iBAAzB,CACE,kBAAC,MAAD;QACE,IAAG;QACH,WAAU;QACV,OAAO,EAAE,OAAO,EAAO,aAAa;kBACrC;QAEI,CAAA,EACL,kBAAC,OAAD;QAAK,OAAO;SAAE,SAAS;SAAQ,KAAK;SAAO,UAAU;SAAQ;kBAA7D,CACE,kBAAC,SAAD;SACE,MAAK;SACL,OAAO;SACP,UAAA;SACA,OAAO;UACL,MAAM;UACN,SAAS;UACT,QAAQ,aAAa,EAAO;UAC5B,cAAc;UACd,UAAU;UACV,iBAAiB,EAAO;UACxB,OAAO,EAAO;UACf;SACD,CAAA,EACF,kBAAC,UAAD;SACE,SAAS;SACT,OAAO;UACL,SAAS;UACT,iBAAiB,IAAc,EAAO,kBAAkB,EAAO;UAC/D,OAAO;UACP,QAAQ;UACR,cAAc;UACd,QAAQ;UACR,UAAU;UACV,YAAY;UACZ,UAAU;UACX;mBAEA,IAAc,YAAY;SACpB,CAAA,CACL;UACE;;MAGV,kBAAC,WAAD;OAAS,mBAAgB;iBAAzB,CACE,kBAAC,MAAD;QACE,IAAG;QACH,WAAU;QACV,OAAO,EAAE,OAAO,EAAO,aAAa;kBACrC;QAEI,CAAA,EACL,kBAAC,OAAD;QAAK,OAAO;SAAE,SAAS;SAAQ,KAAK;SAAO,cAAc;SAAO,UAAU;SAAQ;kBAAlF;SACE,kBAAC,OAAD;UAAK,OAAO;WAAE,MAAM;WAAG,UAAU;WAAY;oBAA7C,CACE,kBAAC,SAAD;WACE,MAAK;WACL,OAAO,IAAoB,EAAkB,OAAO;WACpD,WAAW,MAAM;AAEf,YADA,EAAe,EAAE,OAAO,MAAM,EAC9B,EAAqB,KAAK;;WAE5B,aAAY;WACZ,OAAO;YACL,OAAO;YACP,SAAS;YACT,QAAQ,aAAa,EAAO;YAC5B,cAAc;YACd,UAAU;YACV,iBAAiB,EAAO;YACxB,OAAO,EAAO;YACf;WACD,CAAA,EAED,KAAe,CAAC,KAAqB,EAAc,SAAS,KAC3D,kBAAC,OAAD;WACE,OAAO;YACL,UAAU;YACV,KAAK;YACL,MAAM;YACN,OAAO;YACP,WAAW;YACX,iBAAiB,EAAO;YACxB,QAAQ,aAAa,EAAO;YAC5B,cAAc;YACd,WAAW;YACX,QAAQ;YACR,WAAW;YACX,WAAW;YACZ;qBAEA,EAAc,KAAK,MAClB,kBAAC,UAAD;YAEE,eAAe,EAAqB,EAAO;YAC3C,OAAO;aACL,SAAS;aACT,OAAO;aACP,SAAS;aACT,WAAW;aACX,iBAAiB;aACjB,QAAQ;aACR,QAAQ;aACR,cAAc,aAAa,EAAO;aACnC;sBAZH;aAcE,kBAAC,OAAD;cAAK,OAAO;eAAE,UAAU;eAAQ,YAAY;eAAK,OAAO,EAAO;eAAa;wBACzE,EAAO;cACJ,CAAA;aACL,EAAO,SACN,kBAAC,OAAD;cAAK,OAAO;eAAE,UAAU;eAAQ,OAAO,EAAO;eAAe;wBAAG,EAAO;cAAY,CAAA;aAErF,kBAAC,QAAD;cACE,OAAO;eACL,UAAU;eACV,OAAO,EAAO;eACd,eAAe;eAChB;wBAEA,EAAO;cACH,CAAA;aACA;cA5BF,EAAO,GA4BL,CACT;WACE,CAAA,CAEJ;;SACN,kBAAC,UAAD;UACE,OAAO;UACP,WAAW,MAAM,EAAsB,EAAE,OAAO,MAAyB;UACzE,OAAO;WACL,SAAS;WACT,QAAQ,aAAa,EAAO;WAC5B,cAAc;WACd,UAAU;WACV,iBAAiB,EAAO;WACxB,OAAO,EAAO;WACf;oBAEA,EAAmB,KAAK,MACvB,kBAAC,UAAD;WAAwB,OAAO,EAAI;qBAChC,EAAI;WACE,EAFI,EAAI,MAER,CACT;UACK,CAAA;SACT,kBAAC,UAAD;UACE,SAAS;UACT,UAAU,CAAC,KAAqB;UAChC,OAAO;WACL,SAAS;WACT,iBAAiB,IAAoB,EAAO,kBAAkB,EAAO;WACrE,OAAO,IAAoB,qCAAqC,EAAO;WACvE,QAAQ;WACR,cAAc;WACd,QAAQ,IAAoB,YAAY;WACxC,UAAU;WACV,YAAY;WACb;oBACF;UAEQ,CAAA;SACL;UACE;;MAGV,kBAAC,WAAD;OAAS,mBAAgB;iBAAzB,CACE,kBAAC,OAAD;QACE,OAAO;SACL,SAAS;SACT,YAAY;SACZ,gBAAgB;SAChB,UAAU;SACV,KAAK;SACL,cAAc;SACf;kBARH,CAUE,kBAAC,MAAD;SAAI,IAAG;SAAuB,WAAU;SAAwB,OAAO,EAAE,OAAO,EAAO,aAAa;mBAApG;UAAsG;UAC/E,EAAO;UAAO;UAChC;YACJ,EAAO,SAAS,KACf,kBAAC,UAAD;SACE,SAAS;SACT,OAAO;UACL,SAAS;UACT,iBAAiB;UACjB,OAAO,EAAO;UACd,QAAQ;UACR,QAAQ;UACR,UAAU;UACX;mBACF;SAEQ,CAAA,CAEP;WAEL,KAAW,EAAO,WAAW,IAC5B,kBAAC,KAAD;QAAG,OAAO;SAAE,OAAO,EAAO;SAAe,UAAU;SAAQ;kBAAE;QAAc,CAAA,GACzE,EAAO,WAAW,IACpB,kBAAC,KAAD;QAAG,OAAO;SAAE,OAAO,EAAO;SAAe,UAAU;SAAQ;kBAAE;QAEzD,CAAA,GAEJ,kBAAC,OAAD;QAAK,OAAO;SAAE,SAAS;SAAQ,eAAe;SAAU,KAAK;SAAO;kBACjE,EAAO,KAAK,MACX,kBAAC,GAAD;SAES;SACP,oBAAoB;SACpB,UAAU;SACF;SACR,EALK,EAAM,GAKX,CACF;QACE,CAAA,CAEA;;MACN;;IAEJ;;EACF,CAAA,EAI2B,SAAS,KAAK,GA/V7B;;AA6WtB,SAAS,EAAU,EAAE,UAAO,uBAAoB,aAAU,aAA0B;AAClF,QACE,kBAAC,OAAD;EACE,OAAO;GACL,SAAS;GACT,YAAY;GACZ,gBAAgB;GAChB,SAAS;GACT,iBAAiB,EAAO;GACxB,cAAc;GACf;YARH,CAUE,kBAAC,OAAD;GAAK,OAAO;IAAE,SAAS;IAAQ,YAAY;IAAU,KAAK;IAAQ;aAAlE,CACE,kBAAC,OAAD;IACE,OAAO;KACL,OAAO;KACP,QAAQ;KACR,cAAc;KACd,iBAAiB,EAAO;KACxB,SAAS;KACT,YAAY;KACZ,gBAAgB;KAChB,OAAO;KACP,YAAY;KACZ,UAAU;KACX;cAEA,EAAM,kBAAkB,SAAS,MAAM;IACpC,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,OAAD;IAAK,OAAO;KAAE,UAAU;KAAQ,YAAY;KAAK,OAAO,EAAO;KAAa;cAAG,EAAM;IAAkB,CAAA,EACvG,kBAAC,OAAD;IAAK,OAAO;KAAE,UAAU;KAAQ,OAAO,EAAO;KAAe;cAA7D;KACG,EAAM;KAAc;KAAW,IAAI,KAAK,EAAM,UAAU,CAAC,oBAAoB;KAC1E;MACF,EAAA,CAAA,CACF;MACN,kBAAC,OAAD;GAAK,OAAO;IAAE,SAAS;IAAQ,YAAY;IAAU,KAAK;IAAO;aAAjE,CACE,kBAAC,UAAD;IACE,OAAO,EAAM;IACb,WAAW,MAAM,EAAmB,EAAM,IAAI,EAAE,OAAO,MAAyB;IAChF,OAAO;KACL,SAAS;KACT,QAAQ,aAAa,EAAO;KAC5B,cAAc;KACd,UAAU;KACV,iBAAiB,EAAO;KACxB,OAAO,EAAO;KACf;cAEA,EAAmB,KAAK,MACvB,kBAAC,UAAD;KAAwB,OAAO,EAAI;eAChC,EAAI;KACE,EAFI,EAAI,MAER,CACT;IACK,CAAA,EACT,kBAAC,UAAD;IACE,eAAe,EAAS,EAAM,GAAG;IACjC,OAAO;KACL,SAAS;KACT,iBAAiB;KACjB,OAAO,EAAO;KACd,QAAQ,aAAa,EAAO;KAC5B,cAAc;KACd,QAAQ;KACR,UAAU;KACX;cACF;IAEQ,CAAA,CACL;KACF"}
|
|
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 { formatDistanceToNow } from 'date-fns';\nimport {\n useDashboardSharing,\n type DashboardShare,\n type SharePermission,\n type ShareRecipientType,\n} from '../../../hooks';\nimport type { DashboardVisibility } from '../../../types';\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 * Current general-access level (`Dashboard.visibility`). The General access\n * control renders ONLY when this and `onVisibilityChange` are both supplied —\n * a selector with no known current value would be a guess, and one with no\n * handler would be a switch wired to nothing.\n */\n visibility?: DashboardVisibility;\n /** Persist a new general-access level. Resolves once the write completes. */\n onVisibilityChange?: (visibility: DashboardVisibility) => Promise<void>;\n /** False when the actor lacks `bigconsole-dashboard:update`; the control is then read-only. */\n canChangeVisibility?: boolean;\n}\n\n/** General-access levels, in widening order. */\nconst VISIBILITY_OPTIONS: { value: DashboardVisibility; label: string; description: string }[] = [\n { value: 'PRIVATE', label: 'Private', description: 'Only people added below' },\n { value: 'WORKSPACE', label: 'Workspace', description: 'Anyone in this workspace' },\n { value: 'PUBLIC', label: 'Public', description: 'Anyone with the link' },\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({\n dashboardId,\n dashboardName,\n isOpen,\n onClose,\n visibility,\n onVisibilityChange,\n canChangeVisibility = true,\n}: 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 const [visibilitySaving, setVisibilitySaving] = useState(false);\n const [visibilityError, setVisibilityError] = useState<string | null>(null);\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 const handleVisibilityChange = useCallback(\n async (next: DashboardVisibility) => {\n if (!onVisibilityChange || next === visibility) return;\n setVisibilityError(null);\n setVisibilitySaving(true);\n try {\n await onVisibilityChange(next);\n } catch (err) {\n // Surface the failure: the select snaps back to `visibility`, which the\n // parent has not changed, so a silent catch would look like a success.\n setVisibilityError(err instanceof Error ? err.message : 'Could not update general access.');\n } finally {\n setVisibilitySaving(false);\n }\n },\n [onVisibilityChange, visibility]\n );\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 {/* General access — WHO the link works for. It leads because the\n link below is only as useful as this setting allows: copying a\n URL from a PRIVATE board hands over something the recipient\n cannot open. Backed by `Dashboard.visibility` +\n `BigConsoleUpdateDashboardInput.visibility`. */}\n {visibility && onVisibilityChange && (\n <section aria-labelledby=\"share-visibility-heading\">\n <h3\n id=\"share-visibility-heading\"\n className=\"mb-form-gap text-sm font-semibold\"\n style={{ color: colors.textPrimary }}\n >\n General access\n </h3>\n <div style={{ display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'wrap' }}>\n <select\n aria-label=\"General access\"\n data-testid=\"share-visibility\"\n value={visibility}\n disabled={!canChangeVisibility || visibilitySaving}\n onChange={(e) => void handleVisibilityChange(e.target.value as DashboardVisibility)}\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 cursor: !canChangeVisibility || visibilitySaving ? 'not-allowed' : 'pointer',\n opacity: !canChangeVisibility || visibilitySaving ? 0.6 : 1,\n }}\n >\n {VISIBILITY_OPTIONS.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n <span style={{ fontSize: '12px', color: colors.textSecondary }}>\n {VISIBILITY_OPTIONS.find((opt) => opt.value === visibility)?.description}\n </span>\n </div>\n {!canChangeVisibility && (\n <p style={{ margin: '6px 0 0', fontSize: '11px', color: colors.textSecondary }}>\n You don't have permission to change general access.\n </p>\n )}\n {visibilityError && (\n <p style={{ margin: '6px 0 0', fontSize: '11px', color: colors.statusErrorText }}>\n {visibilityError}\n </p>\n )}\n </section>\n )}\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\n/**\n * Access window for one share.\n *\n * `expiresAt` is selected by DashboardShareFields on every share this dialog\n * loads, but the row never said whether the access it lists ends — so a share\n * that lapsed yesterday looked identical to a permanent one. Expired shares are\n * called out explicitly: the backend leaves the row in place, so \"expired\" is a\n * state the reader has to be told about, not one they can infer from absence.\n */\nfunction describeExpiry(expiresAt: string | undefined): { label: string; expired: boolean } {\n if (!expiresAt) return { label: 'No expiry', expired: false };\n const date = new Date(expiresAt);\n if (Number.isNaN(date.getTime())) return { label: 'No expiry', expired: false };\n if (date.getTime() <= Date.now()) {\n return { label: `Expired ${formatDistanceToNow(date, { addSuffix: true })}`, expired: true };\n }\n return { label: `Expires ${formatDistanceToNow(date, { addSuffix: true })}`, expired: false };\n}\n\nfunction ShareItem({ share, onUpdatePermission, onRevoke, colors }: ShareItemProps) {\n const expiry = describeExpiry(share.expiresAt);\n\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', flexWrap: 'wrap' }}>\n <span\n data-testid=\"share-expiry\"\n title={share.expiresAt ? new Date(share.expiresAt).toLocaleString() : 'Access does not expire'}\n style={{\n fontSize: '11px',\n padding: '4px 8px',\n borderRadius: '999px',\n whiteSpace: 'nowrap',\n backgroundColor: colors.bgTertiary,\n color: expiry.expired ? colors.statusErrorText : colors.textSecondary,\n fontWeight: expiry.expired ? 600 : 400,\n }}\n >\n {expiry.label}\n </span>\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,9 +1,9 @@
|
|
|
1
1
|
import { memo as e } from "react";
|
|
2
|
-
import { Download as t, LayoutTemplate as n,
|
|
3
|
-
import { Badge as
|
|
4
|
-
import { jsx as
|
|
2
|
+
import { Download as t, LayoutTemplate as n, Sparkles as r, Star as i } from "lucide-react";
|
|
3
|
+
import { Badge as a, Spinner as o } from "@burdenoff/fe-libs/ui";
|
|
4
|
+
import { jsx as s, jsxs as c } from "react/jsx-runtime";
|
|
5
5
|
//#region src/bigconsole/components/dashboard/StoreTemplatePicker.tsx
|
|
6
|
-
var
|
|
6
|
+
var l = {
|
|
7
7
|
textAlign: "left",
|
|
8
8
|
padding: "var(--space-cardPadding)",
|
|
9
9
|
borderRadius: "var(--radius-card)",
|
|
@@ -14,34 +14,38 @@ var c = {
|
|
|
14
14
|
flexDirection: "column",
|
|
15
15
|
gap: "var(--space-controlGap)",
|
|
16
16
|
transition: "all 0.15s ease"
|
|
17
|
-
}
|
|
18
|
-
|
|
17
|
+
};
|
|
18
|
+
function u(e) {
|
|
19
|
+
return /^[A-Z0-9_]+$/.test(e) ? e.toLowerCase().split("_").filter(Boolean).map((e) => e.charAt(0).toUpperCase() + e.slice(1)).join(" ") : e;
|
|
20
|
+
}
|
|
21
|
+
var d = e(function({ templates: e, loading: d, error: f, selectedTemplateId: p, onSelect: m }) {
|
|
22
|
+
return d ? /* @__PURE__ */ s("div", {
|
|
19
23
|
style: {
|
|
20
24
|
display: "flex",
|
|
21
25
|
justifyContent: "center",
|
|
22
26
|
padding: "var(--space-pagePadding)"
|
|
23
27
|
},
|
|
24
|
-
children: /* @__PURE__ */ o
|
|
25
|
-
}) :
|
|
28
|
+
children: /* @__PURE__ */ s(o, {})
|
|
29
|
+
}) : f ? /* @__PURE__ */ c("div", {
|
|
26
30
|
style: {
|
|
27
31
|
padding: "var(--space-cardPadding)",
|
|
28
32
|
color: "var(--color-status-error-text)"
|
|
29
33
|
},
|
|
30
|
-
children: ["Could not load templates from the store: ",
|
|
31
|
-
}) : e.length === 0 ? /* @__PURE__ */
|
|
34
|
+
children: ["Could not load templates from the store: ", f]
|
|
35
|
+
}) : e.length === 0 ? /* @__PURE__ */ c("div", {
|
|
32
36
|
style: {
|
|
33
37
|
padding: "var(--space-pagePadding)",
|
|
34
38
|
textAlign: "center",
|
|
35
39
|
color: "var(--color-text-muted)"
|
|
36
40
|
},
|
|
37
41
|
children: [
|
|
38
|
-
/* @__PURE__ */
|
|
42
|
+
/* @__PURE__ */ s(n, { style: {
|
|
39
43
|
height: "var(--size-icon-lg)",
|
|
40
44
|
width: "var(--size-icon-lg)",
|
|
41
45
|
margin: "0 auto var(--space-formGap)"
|
|
42
46
|
} }),
|
|
43
|
-
/* @__PURE__ */
|
|
44
|
-
/* @__PURE__ */
|
|
47
|
+
/* @__PURE__ */ s("div", { children: "No dashboard templates published yet" }),
|
|
48
|
+
/* @__PURE__ */ s("div", {
|
|
45
49
|
style: {
|
|
46
50
|
fontSize: "var(--font-size-body-sm)",
|
|
47
51
|
marginTop: "var(--space-iconGap)"
|
|
@@ -49,40 +53,73 @@ var c = {
|
|
|
49
53
|
children: "Templates published to the store will appear here."
|
|
50
54
|
})
|
|
51
55
|
]
|
|
52
|
-
}) : /* @__PURE__ */
|
|
56
|
+
}) : /* @__PURE__ */ s("div", {
|
|
53
57
|
style: {
|
|
54
58
|
display: "grid",
|
|
55
59
|
gridTemplateColumns: "repeat(auto-fill, minmax(min(220px, 100%), 1fr))",
|
|
56
60
|
gap: "var(--space-formGap)"
|
|
57
61
|
},
|
|
58
62
|
children: e.map((e) => {
|
|
59
|
-
let
|
|
60
|
-
return /* @__PURE__ */
|
|
63
|
+
let o = e.id === p;
|
|
64
|
+
return /* @__PURE__ */ c("button", {
|
|
61
65
|
type: "button",
|
|
62
|
-
onClick: () =>
|
|
63
|
-
"aria-pressed":
|
|
66
|
+
onClick: () => m(e),
|
|
67
|
+
"aria-pressed": o,
|
|
64
68
|
style: {
|
|
65
|
-
...
|
|
66
|
-
borderColor:
|
|
67
|
-
backgroundColor:
|
|
68
|
-
boxShadow:
|
|
69
|
+
...l,
|
|
70
|
+
borderColor: o ? "var(--color-action-primary-border)" : "var(--color-border-subtle)",
|
|
71
|
+
backgroundColor: o ? "var(--color-bg-accentSubtle)" : "var(--color-bg-surface)",
|
|
72
|
+
boxShadow: o ? "0 0 0 2px var(--color-action-primary-bg)" : "none"
|
|
69
73
|
},
|
|
70
74
|
children: [
|
|
71
|
-
/* @__PURE__ */
|
|
75
|
+
/* @__PURE__ */ c("div", {
|
|
72
76
|
style: {
|
|
73
77
|
display: "flex",
|
|
74
78
|
alignItems: "center",
|
|
75
79
|
gap: "var(--space-iconGap)"
|
|
76
80
|
},
|
|
77
|
-
children: [
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
children: [
|
|
82
|
+
/* @__PURE__ */ s(n, { style: {
|
|
83
|
+
height: "var(--size-icon-sm)",
|
|
84
|
+
width: "var(--size-icon-sm)",
|
|
85
|
+
flexShrink: 0
|
|
86
|
+
} }),
|
|
87
|
+
/* @__PURE__ */ s("span", {
|
|
88
|
+
style: {
|
|
89
|
+
fontWeight: "var(--font-weight-medium)",
|
|
90
|
+
minWidth: 0
|
|
91
|
+
},
|
|
92
|
+
children: e.name
|
|
93
|
+
}),
|
|
94
|
+
e.featured && /* @__PURE__ */ c("span", {
|
|
95
|
+
"data-testid": "template-featured",
|
|
96
|
+
title: "Featured in the store",
|
|
97
|
+
style: {
|
|
98
|
+
marginLeft: "auto",
|
|
99
|
+
display: "inline-flex",
|
|
100
|
+
alignItems: "center",
|
|
101
|
+
gap: "2px",
|
|
102
|
+
flexShrink: 0,
|
|
103
|
+
padding: "2px 6px",
|
|
104
|
+
borderRadius: "var(--radius-pill)",
|
|
105
|
+
backgroundColor: "var(--color-status-warning-bgSubtle)",
|
|
106
|
+
color: "var(--color-status-warning-text)",
|
|
107
|
+
fontSize: "var(--font-size-xs)",
|
|
108
|
+
fontWeight: "var(--font-weight-semibold)",
|
|
109
|
+
textTransform: "uppercase",
|
|
110
|
+
letterSpacing: "0.02em"
|
|
111
|
+
},
|
|
112
|
+
children: [/* @__PURE__ */ s(r, {
|
|
113
|
+
style: {
|
|
114
|
+
height: "10px",
|
|
115
|
+
width: "10px"
|
|
116
|
+
},
|
|
117
|
+
"aria-hidden": "true"
|
|
118
|
+
}), "Featured"]
|
|
119
|
+
})
|
|
120
|
+
]
|
|
84
121
|
}),
|
|
85
|
-
/* @__PURE__ */
|
|
122
|
+
/* @__PURE__ */ s("div", {
|
|
86
123
|
style: {
|
|
87
124
|
fontSize: "var(--font-size-body-sm)",
|
|
88
125
|
color: "var(--color-text-muted)",
|
|
@@ -93,7 +130,7 @@ var c = {
|
|
|
93
130
|
},
|
|
94
131
|
children: e.description || "No description provided"
|
|
95
132
|
}),
|
|
96
|
-
/* @__PURE__ */
|
|
133
|
+
/* @__PURE__ */ c("div", {
|
|
97
134
|
style: {
|
|
98
135
|
display: "flex",
|
|
99
136
|
alignItems: "center",
|
|
@@ -104,31 +141,42 @@ var c = {
|
|
|
104
141
|
color: "var(--color-text-muted)"
|
|
105
142
|
},
|
|
106
143
|
children: [
|
|
107
|
-
e.requiresPurchase ? /* @__PURE__ */
|
|
144
|
+
e.requiresPurchase ? /* @__PURE__ */ c(a, {
|
|
108
145
|
variant: "secondary",
|
|
109
146
|
children: ["$", e.price]
|
|
110
|
-
}) : /* @__PURE__ */
|
|
147
|
+
}) : /* @__PURE__ */ s(a, {
|
|
111
148
|
variant: "secondary",
|
|
112
149
|
children: "Free"
|
|
113
150
|
}),
|
|
114
|
-
e.
|
|
151
|
+
e.category && /* @__PURE__ */ s("span", {
|
|
152
|
+
"data-testid": "template-category",
|
|
153
|
+
style: {
|
|
154
|
+
padding: "2px 6px",
|
|
155
|
+
borderRadius: "var(--radius-pill)",
|
|
156
|
+
backgroundColor: "var(--color-bg-sunken)",
|
|
157
|
+
color: "var(--color-text-secondary)",
|
|
158
|
+
whiteSpace: "nowrap"
|
|
159
|
+
},
|
|
160
|
+
children: u(e.category)
|
|
161
|
+
}),
|
|
162
|
+
e.downloads > 0 && /* @__PURE__ */ c("span", {
|
|
115
163
|
style: {
|
|
116
164
|
display: "inline-flex",
|
|
117
165
|
alignItems: "center",
|
|
118
166
|
gap: "2px"
|
|
119
167
|
},
|
|
120
|
-
children: [/* @__PURE__ */
|
|
168
|
+
children: [/* @__PURE__ */ s(t, { style: {
|
|
121
169
|
height: "12px",
|
|
122
170
|
width: "12px"
|
|
123
171
|
} }), e.downloads]
|
|
124
172
|
}),
|
|
125
|
-
e.rating ? /* @__PURE__ */
|
|
173
|
+
e.rating ? /* @__PURE__ */ c("span", {
|
|
126
174
|
style: {
|
|
127
175
|
display: "inline-flex",
|
|
128
176
|
alignItems: "center",
|
|
129
177
|
gap: "2px"
|
|
130
178
|
},
|
|
131
|
-
children: [/* @__PURE__ */
|
|
179
|
+
children: [/* @__PURE__ */ s(i, { style: {
|
|
132
180
|
height: "12px",
|
|
133
181
|
width: "12px"
|
|
134
182
|
} }), e.rating.toFixed(1)]
|
|
@@ -141,6 +189,6 @@ var c = {
|
|
|
141
189
|
});
|
|
142
190
|
});
|
|
143
191
|
//#endregion
|
|
144
|
-
export {
|
|
192
|
+
export { d as default };
|
|
145
193
|
|
|
146
194
|
//# sourceMappingURL=StoreTemplatePicker.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StoreTemplatePicker.js","names":[],"sources":["../../../../src/bigconsole/components/dashboard/StoreTemplatePicker.tsx"],"sourcesContent":["/**\n * StoreTemplatePicker\n *\n * Renders dashboard templates published to the global store as selectable\n * cards. Replaces the workspace-table-backed TemplatePicker for the \"From\n * Template\" flow, which surfaced nothing because no workspace templates have\n * ever been authored.\n *\n * @module bigconsole/components/dashboard/StoreTemplatePicker\n */\nimport { Badge, Spinner } from '@burdenoff/fe-libs/ui';\nimport { Download, LayoutTemplate, Star } from 'lucide-react';\nimport { type FC, memo } from 'react';\n\nimport type { StoreDashboardTemplate } from '../../hooks/useStoreDashboardTemplates';\n\nexport interface StoreTemplatePickerProps {\n templates: StoreDashboardTemplate[];\n loading: boolean;\n error: string | null;\n selectedTemplateId?: string;\n onSelect: (template: StoreDashboardTemplate) => void;\n}\n\nconst cardBase: React.CSSProperties = {\n textAlign: 'left',\n padding: 'var(--space-cardPadding)',\n borderRadius: 'var(--radius-card)',\n border: '1px solid var(--color-border-default)',\n background: 'var(--color-bg-surface)',\n cursor: 'pointer',\n display: 'flex',\n flexDirection: 'column',\n gap: 'var(--space-controlGap)',\n transition: 'all 0.15s ease',\n};\n\nexport const StoreTemplatePicker: FC<StoreTemplatePickerProps> = memo(function StoreTemplatePicker({\n templates,\n loading,\n error,\n selectedTemplateId,\n onSelect,\n}) {\n if (loading) {\n return (\n <div style={{ display: 'flex', justifyContent: 'center', padding: 'var(--space-pagePadding)' }}>\n <Spinner />\n </div>\n );\n }\n\n if (error) {\n return (\n <div style={{ padding: 'var(--space-cardPadding)', color: 'var(--color-status-error-text)' }}>\n Could not load templates from the store: {error}\n </div>\n );\n }\n\n if (templates.length === 0) {\n return (\n <div\n style={{\n padding: 'var(--space-pagePadding)',\n textAlign: 'center',\n color: 'var(--color-text-muted)',\n }}\n >\n <LayoutTemplate\n style={{\n height: 'var(--size-icon-lg)',\n width: 'var(--size-icon-lg)',\n margin: '0 auto var(--space-formGap)',\n }}\n />\n <div>No dashboard templates published yet</div>\n <div style={{ fontSize: 'var(--font-size-body-sm)', marginTop: 'var(--space-iconGap)' }}>\n Templates published to the store will appear here.\n </div>\n </div>\n );\n }\n\n return (\n <div\n style={{\n display: 'grid',\n gridTemplateColumns: 'repeat(auto-fill, minmax(min(220px, 100%), 1fr))',\n gap: 'var(--space-formGap)',\n }}\n >\n {templates.map((template) => {\n const selected = template.id === selectedTemplateId;\n return (\n <button\n key={template.id}\n type=\"button\"\n onClick={() => onSelect(template)}\n aria-pressed={selected}\n style={{\n ...cardBase,\n // Selection needs to survive a glance across a 900px grid: brand\n // border + ring + tinted surface, not a 1-step elevation alone.\n borderColor: selected ? 'var(--color-action-primary-border)' : 'var(--color-border-subtle)',\n backgroundColor: selected ? 'var(--color-bg-accentSubtle)' : 'var(--color-bg-surface)',\n boxShadow: selected ? '0 0 0 2px var(--color-action-primary-bg)' : 'none',\n }}\n >\n <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-iconGap)' }}>\n <LayoutTemplate style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />\n <span style={{ fontWeight: 'var(--font-weight-medium)' }}>{template.name}</span>\n </div>\n\n <div\n style={{\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-text-muted)',\n display: '-webkit-box',\n WebkitLineClamp: 2,\n WebkitBoxOrient: 'vertical',\n overflow: 'hidden',\n }}\n >\n {template.description || 'No description provided'}\n </div>\n\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-controlGap)',\n marginTop: 'auto',\n flexWrap: 'wrap',\n // `--font-size-body-xs` does not exist in the token build — using it\n // drops the declaration and the row silently inherits body size.\n fontSize: 'var(--font-size-xs)',\n color: 'var(--color-text-muted)',\n }}\n >\n {template.requiresPurchase ? (\n <Badge variant=\"secondary\">${template.price}</Badge>\n ) : (\n <Badge variant=\"secondary\">Free</Badge>\n )}\n {template.downloads > 0 && (\n <span style={{ display: 'inline-flex', alignItems: 'center', gap: '2px' }}>\n <Download style={{ height: '12px', width: '12px' }} />\n {template.downloads}\n </span>\n )}\n {template.rating ? (\n <span style={{ display: 'inline-flex', alignItems: 'center', gap: '2px' }}>\n <Star style={{ height: '12px', width: '12px' }} />\n {template.rating.toFixed(1)}\n </span>\n ) : null}\n </div>\n </button>\n );\n })}\n </div>\n );\n});\n\nexport default StoreTemplatePicker;\n"],"mappings":";;;;;AAwBA,IAAM,IAAgC;CACpC,WAAW;CACX,SAAS;CACT,cAAc;CACd,QAAQ;CACR,YAAY;CACZ,QAAQ;CACR,SAAS;CACT,eAAe;CACf,KAAK;CACL,YAAY;CACb,EAEY,IAAoD,EAAK,SAA6B,EACjG,cACA,YACA,UACA,uBACA,eACC;AAyCD,QAxCI,IAEA,kBAAC,OAAD;EAAK,OAAO;GAAE,SAAS;GAAQ,gBAAgB;GAAU,SAAS;GAA4B;YAC5F,kBAAC,GAAD,EAAW,CAAA;EACP,CAAA,GAIN,IAEA,kBAAC,OAAD;EAAK,OAAO;GAAE,SAAS;GAA4B,OAAO;GAAkC;YAA5F,CAA8F,6CAClD,EACtC;MAIN,EAAU,WAAW,IAErB,kBAAC,OAAD;EACE,OAAO;GACL,SAAS;GACT,WAAW;GACX,OAAO;GACR;YALH;GAOE,kBAAC,GAAD,EACE,OAAO;IACL,QAAQ;IACR,OAAO;IACP,QAAQ;IACT,EACD,CAAA;GACF,kBAAC,OAAD,EAAA,UAAK,wCAA0C,CAAA;GAC/C,kBAAC,OAAD;IAAK,OAAO;KAAE,UAAU;KAA4B,WAAW;KAAwB;cAAE;IAEnF,CAAA;GACF;MAKR,kBAAC,OAAD;EACE,OAAO;GACL,SAAS;GACT,qBAAqB;GACrB,KAAK;GACN;YAEA,EAAU,KAAK,MAAa;GAC3B,IAAM,IAAW,EAAS,OAAO;AACjC,UACE,kBAAC,UAAD;IAEE,MAAK;IACL,eAAe,EAAS,EAAS;IACjC,gBAAc;IACd,OAAO;KACL,GAAG;KAGH,aAAa,IAAW,uCAAuC;KAC/D,iBAAiB,IAAW,iCAAiC;KAC7D,WAAW,IAAW,6CAA6C;KACpE;cAZH;KAcE,kBAAC,OAAD;MAAK,OAAO;OAAE,SAAS;OAAQ,YAAY;OAAU,KAAK;OAAwB;gBAAlF,CACE,kBAAC,GAAD,EAAgB,OAAO;OAAE,QAAQ;OAAuB,OAAO;OAAuB,EAAI,CAAA,EAC1F,kBAAC,QAAD;OAAM,OAAO,EAAE,YAAY,6BAA6B;iBAAG,EAAS;OAAY,CAAA,CAC5E;;KAEN,kBAAC,OAAD;MACE,OAAO;OACL,UAAU;OACV,OAAO;OACP,SAAS;OACT,iBAAiB;OACjB,iBAAiB;OACjB,UAAU;OACX;gBAEA,EAAS,eAAe;MACrB,CAAA;KAEN,kBAAC,OAAD;MACE,OAAO;OACL,SAAS;OACT,YAAY;OACZ,KAAK;OACL,WAAW;OACX,UAAU;OAGV,UAAU;OACV,OAAO;OACR;gBAXH;OAaG,EAAS,mBACR,kBAAC,GAAD;QAAO,SAAQ;kBAAf,CAA2B,KAAE,EAAS,MAAc;YAEpD,kBAAC,GAAD;QAAO,SAAQ;kBAAY;QAAY,CAAA;OAExC,EAAS,YAAY,KACpB,kBAAC,QAAD;QAAM,OAAO;SAAE,SAAS;SAAe,YAAY;SAAU,KAAK;SAAO;kBAAzE,CACE,kBAAC,GAAD,EAAU,OAAO;SAAE,QAAQ;SAAQ,OAAO;SAAQ,EAAI,CAAA,EACrD,EAAS,UACL;;OAER,EAAS,SACR,kBAAC,QAAD;QAAM,OAAO;SAAE,SAAS;SAAe,YAAY;SAAU,KAAK;SAAO;kBAAzE,CACE,kBAAC,GAAD,EAAM,OAAO;SAAE,QAAQ;SAAQ,OAAO;SAAQ,EAAI,CAAA,EACjD,EAAS,OAAO,QAAQ,EAAE,CACtB;YACL;OACA;;KACC;MA9DF,EAAS,GA8DP;IAEX;EACE,CAAA;EAER"}
|
|
1
|
+
{"version":3,"file":"StoreTemplatePicker.js","names":[],"sources":["../../../../src/bigconsole/components/dashboard/StoreTemplatePicker.tsx"],"sourcesContent":["/**\n * StoreTemplatePicker\n *\n * Renders dashboard templates published to the global store as selectable\n * cards. Replaces the workspace-table-backed TemplatePicker for the \"From\n * Template\" flow, which surfaced nothing because no workspace templates have\n * ever been authored.\n *\n * @module bigconsole/components/dashboard/StoreTemplatePicker\n */\nimport { Badge, Spinner } from '@burdenoff/fe-libs/ui';\nimport { Download, LayoutTemplate, Sparkles, Star } from 'lucide-react';\nimport { type FC, memo } from 'react';\n\nimport type { StoreDashboardTemplate } from '../../hooks/useStoreDashboardTemplates';\n\nexport interface StoreTemplatePickerProps {\n templates: StoreDashboardTemplate[];\n loading: boolean;\n error: string | null;\n selectedTemplateId?: string;\n onSelect: (template: StoreDashboardTemplate) => void;\n}\n\nconst cardBase: React.CSSProperties = {\n textAlign: 'left',\n padding: 'var(--space-cardPadding)',\n borderRadius: 'var(--radius-card)',\n border: '1px solid var(--color-border-default)',\n background: 'var(--color-bg-surface)',\n cursor: 'pointer',\n display: 'flex',\n flexDirection: 'column',\n gap: 'var(--space-controlGap)',\n transition: 'all 0.15s ease',\n};\n\n/**\n * `StoreProduct.category` is a free-form String column, so publishers write it\n * either as prose (\"Operations\") or as a machine constant (\"SALES_PIPELINE\").\n * Only the machine form is rewritten; anything already written for humans is\n * left exactly as the publisher entered it.\n */\nfunction formatCategoryLabel(raw: string): string {\n if (!/^[A-Z0-9_]+$/.test(raw)) return raw;\n return raw\n .toLowerCase()\n .split('_')\n .filter(Boolean)\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(' ');\n}\n\nexport const StoreTemplatePicker: FC<StoreTemplatePickerProps> = memo(function StoreTemplatePicker({\n templates,\n loading,\n error,\n selectedTemplateId,\n onSelect,\n}) {\n if (loading) {\n return (\n <div style={{ display: 'flex', justifyContent: 'center', padding: 'var(--space-pagePadding)' }}>\n <Spinner />\n </div>\n );\n }\n\n if (error) {\n return (\n <div style={{ padding: 'var(--space-cardPadding)', color: 'var(--color-status-error-text)' }}>\n Could not load templates from the store: {error}\n </div>\n );\n }\n\n if (templates.length === 0) {\n return (\n <div\n style={{\n padding: 'var(--space-pagePadding)',\n textAlign: 'center',\n color: 'var(--color-text-muted)',\n }}\n >\n <LayoutTemplate\n style={{\n height: 'var(--size-icon-lg)',\n width: 'var(--size-icon-lg)',\n margin: '0 auto var(--space-formGap)',\n }}\n />\n <div>No dashboard templates published yet</div>\n <div style={{ fontSize: 'var(--font-size-body-sm)', marginTop: 'var(--space-iconGap)' }}>\n Templates published to the store will appear here.\n </div>\n </div>\n );\n }\n\n return (\n <div\n style={{\n display: 'grid',\n gridTemplateColumns: 'repeat(auto-fill, minmax(min(220px, 100%), 1fr))',\n gap: 'var(--space-formGap)',\n }}\n >\n {templates.map((template) => {\n const selected = template.id === selectedTemplateId;\n return (\n <button\n key={template.id}\n type=\"button\"\n onClick={() => onSelect(template)}\n aria-pressed={selected}\n style={{\n ...cardBase,\n // Selection needs to survive a glance across a 900px grid: brand\n // border + ring + tinted surface, not a 1-step elevation alone.\n borderColor: selected ? 'var(--color-action-primary-border)' : 'var(--color-border-subtle)',\n backgroundColor: selected ? 'var(--color-bg-accentSubtle)' : 'var(--color-bg-surface)',\n boxShadow: selected ? '0 0 0 2px var(--color-action-primary-bg)' : 'none',\n }}\n >\n <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-iconGap)' }}>\n <LayoutTemplate style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)', flexShrink: 0 }} />\n <span style={{ fontWeight: 'var(--font-weight-medium)', minWidth: 0 }}>{template.name}</span>\n {/* `featured` ships on every browseStore product and was dropped\n on the floor — it is the store's own ranking signal and the one\n thing that tells a first-time author where to start. */}\n {template.featured && (\n <span\n data-testid=\"template-featured\"\n title=\"Featured in the store\"\n style={{\n marginLeft: 'auto',\n display: 'inline-flex',\n alignItems: 'center',\n gap: '2px',\n flexShrink: 0,\n padding: '2px 6px',\n borderRadius: 'var(--radius-pill)',\n backgroundColor: 'var(--color-status-warning-bgSubtle)',\n color: 'var(--color-status-warning-text)',\n fontSize: 'var(--font-size-xs)',\n fontWeight: 'var(--font-weight-semibold)',\n textTransform: 'uppercase',\n letterSpacing: '0.02em',\n }}\n >\n <Sparkles style={{ height: '10px', width: '10px' }} aria-hidden=\"true\" />\n Featured\n </span>\n )}\n </div>\n\n <div\n style={{\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-text-muted)',\n display: '-webkit-box',\n WebkitLineClamp: 2,\n WebkitBoxOrient: 'vertical',\n overflow: 'hidden',\n }}\n >\n {template.description || 'No description provided'}\n </div>\n\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-controlGap)',\n marginTop: 'auto',\n flexWrap: 'wrap',\n // `--font-size-body-xs` does not exist in the token build — using it\n // drops the declaration and the row silently inherits body size.\n fontSize: 'var(--font-size-xs)',\n color: 'var(--color-text-muted)',\n }}\n >\n {template.requiresPurchase ? (\n <Badge variant=\"secondary\">${template.price}</Badge>\n ) : (\n <Badge variant=\"secondary\">Free</Badge>\n )}\n {/* `category` is fetched by BrowseDashboardTemplates and was never\n shown; it is what makes a wall of template cards scannable. */}\n {template.category && (\n <span\n data-testid=\"template-category\"\n style={{\n padding: '2px 6px',\n borderRadius: 'var(--radius-pill)',\n backgroundColor: 'var(--color-bg-sunken)',\n color: 'var(--color-text-secondary)',\n whiteSpace: 'nowrap',\n }}\n >\n {formatCategoryLabel(template.category)}\n </span>\n )}\n {template.downloads > 0 && (\n <span style={{ display: 'inline-flex', alignItems: 'center', gap: '2px' }}>\n <Download style={{ height: '12px', width: '12px' }} />\n {template.downloads}\n </span>\n )}\n {template.rating ? (\n <span style={{ display: 'inline-flex', alignItems: 'center', gap: '2px' }}>\n <Star style={{ height: '12px', width: '12px' }} />\n {template.rating.toFixed(1)}\n </span>\n ) : null}\n </div>\n </button>\n );\n })}\n </div>\n );\n});\n\nexport default StoreTemplatePicker;\n"],"mappings":"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"}
|