@burdenoff/microfe-workspaces 2026.1006.1 → 2026.1013.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/WorkspacesRoot.js +0 -2
- package/dist/WorkspacesRoutes.js +0 -2
- package/dist/components/CreateProjectDialog.js +0 -2
- package/dist/components/CreateWorkspaceDialog.js +0 -2
- package/dist/components/InviteMemberDialog.js +0 -2
- package/dist/components/MemberListItem.js +0 -2
- package/dist/components/ProjectCard.js +0 -2
- package/dist/components/ProjectMemberPickerDialog.js +0 -2
- package/dist/components/RenameProjectDialog.js +0 -2
- package/dist/components/WorkspaceCard.js +0 -2
- package/dist/components/WorkspaceContextPathSync.js +0 -2
- package/dist/components/WorkspacesErrorBoundary.js +0 -2
- package/dist/components/tags/ProjectTagsWidget.js +0 -2
- package/dist/constants/permissions.js +0 -2
- package/dist/generated/global-operations.js +0 -2
- package/dist/generated/wspace-operations.js +0 -2
- package/dist/hooks/useMemberMutations.js +0 -2
- package/dist/hooks/useOrgsWithPermission.js +0 -2
- package/dist/hooks/usePendingInvitations.js +0 -2
- package/dist/hooks/useProjectMemberMutations.js +0 -2
- package/dist/hooks/useProjectMembers.js +0 -2
- package/dist/hooks/useProjectMutations.js +0 -2
- package/dist/hooks/useProjects.js +0 -2
- package/dist/hooks/useScopedNavigation.js +0 -2
- package/dist/hooks/useTenants.js +0 -2
- package/dist/hooks/useTr.js +0 -2
- package/dist/hooks/useWorkspaceActivity.js +0 -2
- package/dist/hooks/useWorkspaceMembers.js +0 -2
- package/dist/hooks/useWorkspaceMutations.js +0 -2
- package/dist/hooks/useWorkspacePermissions.js +0 -2
- package/dist/hooks/useWorkspaceRoles.js +0 -2
- package/dist/hooks/useWorkspaces.js +0 -2
- package/dist/hooks/useWorkspacesEventEmitter.js +0 -2
- package/dist/hooks/useWorkspacesPageView.js +0 -2
- package/dist/pages/ActivityListPage.js +0 -2
- package/dist/pages/CreateWorkspacePage.js +0 -2
- package/dist/pages/InviteAcceptancePage.js +0 -2
- package/dist/pages/MembersListPage.js +0 -2
- package/dist/pages/PendingInvitesPage.js +0 -2
- package/dist/pages/ProjectDetailPage.js +0 -2
- package/dist/pages/ProjectMembersPage.js +0 -2
- package/dist/pages/ProjectsListPage.js +0 -2
- package/dist/pages/WorkspaceDetailPage.js +0 -2
- package/dist/pages/WorkspaceInvitePage.js +0 -2
- package/dist/pages/WorkspaceSettingsPage.js +0 -2
- package/dist/pages/WorkspacesListPage.js +0 -2
- package/dist/pages/dashboard/DashboardCustomizer.js +0 -2
- package/dist/pages/dashboard/DashboardGrid.js +0 -2
- package/dist/pages/dashboard/DashboardPage.js +0 -2
- package/dist/pages/dashboard/WidgetWrapper.js +0 -2
- package/dist/pages/dashboard/config.js +0 -2
- package/dist/pages/dashboard/hooks/useDashboardData.js +0 -2
- package/dist/pages/dashboard/hooks/useDashboardPreferences.js +0 -2
- package/dist/pages/dashboard/reorder.js +0 -2
- package/dist/pages/dashboard/widgets/AgentsWidget.js +0 -2
- package/dist/pages/dashboard/widgets/GroupsWidget.js +0 -2
- package/dist/pages/dashboard/widgets/MembersWidget.js +0 -2
- package/dist/pages/dashboard/widgets/ProjectsWidget.js +0 -2
- package/dist/pages/dashboard/widgets/QuickActionsWidget.js +0 -2
- package/dist/pages/dashboard/widgets/RecentActivityWidget.js +0 -2
- package/dist/pages/dashboard/widgets/SessionsWidget.js +0 -2
- package/dist/pages/dashboard/widgets/StorageWidget.js +0 -2
- package/dist/pages/dashboard/widgets/TagCloudWidget.js +0 -2
- package/dist/pages/dashboard/widgets/TipsWidget.js +0 -2
- package/dist/pages/dashboard/widgets/UpcomingEventsWidget.js +0 -2
- package/dist/pages/dashboard/widgets/VibesWidget.js +0 -2
- package/dist/pages/dashboard/widgets/WelcomeWidget.js +0 -2
- package/dist/pages/dashboard/widgets/WorkflowRunsWidget.js +0 -2
- package/dist/pages/dashboard/widgets/WorkflowStatsWidget.js +0 -2
- package/dist/pages/dashboard/widgets/WorkflowsOverviewWidget.js +0 -2
- package/dist/pages/dashboard/widgets/WorkspacesWidget.js +0 -2
- package/dist/providers/WorkspacesProvider.js +0 -2
- package/dist/store/workspacesStore.js +0 -2
- package/dist/translations/en.js +0 -2
- package/dist/utils/formatters.js +0 -2
- package/dist/utils/i18n.js +0 -2
- package/dist/utils/invitationDisplay.js +0 -2
- package/dist/utils/projectTags.js +0 -2
- package/dist/utils/slug.js +0 -2
- package/dist/utils/telemetryError.js +0 -2
- package/dist/utils/tokenHelpers.js +0 -2
- package/dist/utils/workspaceBulkInviteCsv.js +0 -2
- package/package.json +3 -2
- package/dist/WorkspacesRoot.js.map +0 -1
- package/dist/WorkspacesRoutes.js.map +0 -1
- package/dist/components/CreateProjectDialog.js.map +0 -1
- package/dist/components/CreateWorkspaceDialog.js.map +0 -1
- package/dist/components/InviteMemberDialog.js.map +0 -1
- package/dist/components/MemberListItem.js.map +0 -1
- package/dist/components/ProjectCard.js.map +0 -1
- package/dist/components/ProjectMemberPickerDialog.js.map +0 -1
- package/dist/components/RenameProjectDialog.js.map +0 -1
- package/dist/components/WorkspaceCard.js.map +0 -1
- package/dist/components/WorkspaceContextPathSync.js.map +0 -1
- package/dist/components/WorkspacesErrorBoundary.js.map +0 -1
- package/dist/components/tags/ProjectTagsWidget.js.map +0 -1
- package/dist/constants/permissions.js.map +0 -1
- package/dist/generated/global-operations.js.map +0 -1
- package/dist/generated/wspace-operations.js.map +0 -1
- package/dist/hooks/useMemberMutations.js.map +0 -1
- package/dist/hooks/useOrgsWithPermission.js.map +0 -1
- package/dist/hooks/usePendingInvitations.js.map +0 -1
- package/dist/hooks/useProjectMemberMutations.js.map +0 -1
- package/dist/hooks/useProjectMembers.js.map +0 -1
- package/dist/hooks/useProjectMutations.js.map +0 -1
- package/dist/hooks/useProjects.js.map +0 -1
- package/dist/hooks/useScopedNavigation.js.map +0 -1
- package/dist/hooks/useTenants.js.map +0 -1
- package/dist/hooks/useTr.js.map +0 -1
- package/dist/hooks/useWorkspaceActivity.js.map +0 -1
- package/dist/hooks/useWorkspaceMembers.js.map +0 -1
- package/dist/hooks/useWorkspaceMutations.js.map +0 -1
- package/dist/hooks/useWorkspacePermissions.js.map +0 -1
- package/dist/hooks/useWorkspaceRoles.js.map +0 -1
- package/dist/hooks/useWorkspaces.js.map +0 -1
- package/dist/hooks/useWorkspacesEventEmitter.js.map +0 -1
- package/dist/hooks/useWorkspacesPageView.js.map +0 -1
- package/dist/pages/ActivityListPage.js.map +0 -1
- package/dist/pages/CreateWorkspacePage.js.map +0 -1
- package/dist/pages/InviteAcceptancePage.js.map +0 -1
- package/dist/pages/MembersListPage.js.map +0 -1
- package/dist/pages/PendingInvitesPage.js.map +0 -1
- package/dist/pages/ProjectDetailPage.js.map +0 -1
- package/dist/pages/ProjectMembersPage.js.map +0 -1
- package/dist/pages/ProjectsListPage.js.map +0 -1
- package/dist/pages/WorkspaceDetailPage.js.map +0 -1
- package/dist/pages/WorkspaceInvitePage.js.map +0 -1
- package/dist/pages/WorkspaceSettingsPage.js.map +0 -1
- package/dist/pages/WorkspacesListPage.js.map +0 -1
- package/dist/pages/dashboard/DashboardCustomizer.js.map +0 -1
- package/dist/pages/dashboard/DashboardGrid.js.map +0 -1
- package/dist/pages/dashboard/DashboardPage.js.map +0 -1
- package/dist/pages/dashboard/WidgetWrapper.js.map +0 -1
- package/dist/pages/dashboard/config.js.map +0 -1
- package/dist/pages/dashboard/hooks/useDashboardData.js.map +0 -1
- package/dist/pages/dashboard/hooks/useDashboardPreferences.js.map +0 -1
- package/dist/pages/dashboard/reorder.js.map +0 -1
- package/dist/pages/dashboard/widgets/AgentsWidget.js.map +0 -1
- package/dist/pages/dashboard/widgets/GroupsWidget.js.map +0 -1
- package/dist/pages/dashboard/widgets/MembersWidget.js.map +0 -1
- package/dist/pages/dashboard/widgets/ProjectsWidget.js.map +0 -1
- package/dist/pages/dashboard/widgets/QuickActionsWidget.js.map +0 -1
- package/dist/pages/dashboard/widgets/RecentActivityWidget.js.map +0 -1
- package/dist/pages/dashboard/widgets/SessionsWidget.js.map +0 -1
- package/dist/pages/dashboard/widgets/StorageWidget.js.map +0 -1
- package/dist/pages/dashboard/widgets/TagCloudWidget.js.map +0 -1
- package/dist/pages/dashboard/widgets/TipsWidget.js.map +0 -1
- package/dist/pages/dashboard/widgets/UpcomingEventsWidget.js.map +0 -1
- package/dist/pages/dashboard/widgets/VibesWidget.js.map +0 -1
- package/dist/pages/dashboard/widgets/WelcomeWidget.js.map +0 -1
- package/dist/pages/dashboard/widgets/WorkflowRunsWidget.js.map +0 -1
- package/dist/pages/dashboard/widgets/WorkflowStatsWidget.js.map +0 -1
- package/dist/pages/dashboard/widgets/WorkflowsOverviewWidget.js.map +0 -1
- package/dist/pages/dashboard/widgets/WorkspacesWidget.js.map +0 -1
- package/dist/providers/WorkspacesProvider.js.map +0 -1
- package/dist/store/workspacesStore.js.map +0 -1
- package/dist/translations/en.js.map +0 -1
- package/dist/utils/formatters.js.map +0 -1
- package/dist/utils/i18n.js.map +0 -1
- package/dist/utils/invitationDisplay.js.map +0 -1
- package/dist/utils/projectTags.js.map +0 -1
- package/dist/utils/slug.js.map +0 -1
- package/dist/utils/telemetryError.js.map +0 -1
- package/dist/utils/tokenHelpers.js.map +0 -1
- package/dist/utils/workspaceBulkInviteCsv.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"WorkspacesListPage.js","names":[],"sources":["../../src/pages/WorkspacesListPage.tsx"],"sourcesContent":["import { Plus, Grid3X3, List as ListIcon, Search, Mail, UserPlus } from 'lucide-react';\nimport {\n Badge,\n Button,\n Input,\n Skeleton,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n AuroraBackground,\n GradientText,\n IllustratedEmptyState,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { MoreHorizontal } from 'lucide-react';\nimport { useNavigate } from 'react-router';\nimport { useWorkspacesStore } from '../store/workspacesStore';\nimport { useMyWorkspaces } from '../hooks/useWorkspaces';\nimport { useMyPendingInvitations } from '../hooks/usePendingInvitations';\nimport { WorkspaceCard } from '../components/WorkspaceCard';\nimport { useWorkspacesContext } from '../providers/WorkspacesProvider';\nimport { useDebounce } from '@burdenoff/fe-libs/shared/hooks';\nimport { PermissionButton } from '@burdenoff/fe-libs/shared/components';\nimport { WORKSPACE_PERMISSIONS } from '../constants/permissions';\nimport { useState, useEffect } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { tWithFallback } from '../utils/i18n';\nimport { useScopedNavigation } from '../hooks/useScopedNavigation';\nimport { useWorkspacesPageView } from '../hooks/useWorkspacesPageView';\n\nexport function WorkspacesListPage() {\n useWorkspacesPageView('workspaces.list');\n const { t } = useI18n();\n\n const navigate = useNavigate();\n const { basePath = '/', currentUser, workspaceId: currentWorkspaceId } = useWorkspacesContext();\n const { withCurrentSearch } = useScopedNavigation();\n const { viewMode, setViewMode, workspaceFilters, setWorkspaceFilters } = useWorkspacesStore();\n\n // Navigate to unified workspace creation page\n const handleCreateWorkspace = () => {\n navigate(withCurrentSearch(`${basePath}/new`));\n };\n\n const [searchValue, setSearchValue] = useState(workspaceFilters.search || '');\n const debouncedSearch = useDebounce(searchValue, 500);\n\n // Update filters when debounced search changes\n useEffect(() => {\n if (debouncedSearch !== workspaceFilters.search) {\n setWorkspaceFilters({ search: debouncedSearch });\n }\n }, [debouncedSearch, workspaceFilters.search, setWorkspaceFilters]);\n\n const { data: rawData, isLoading, error } = useMyWorkspaces();\n const { data: pendingInvitations = [] } = useMyPendingInvitations(currentUser?.email);\n const pendingInvitesCount = pendingInvitations.length;\n\n const handleOpenPendingInvites = () => {\n navigate(withCurrentSearch(`${basePath}/invites`));\n };\n\n const handleInviteMember = () => {\n if (!currentWorkspaceId) {\n return;\n }\n\n navigate(withCurrentSearch(`${basePath}/${currentWorkspaceId}/invite`));\n };\n\n // Client-side search filtering (myWorkspaces doesn't accept filter args)\n const data = rawData\n ? {\n ...rawData,\n items: debouncedSearch\n ? rawData.items.filter((ws) =>\n ws.name.toLowerCase().includes(debouncedSearch.toLowerCase())\n )\n : rawData.items,\n total: debouncedSearch\n ? rawData.items.filter((ws) =>\n ws.name.toLowerCase().includes(debouncedSearch.toLowerCase())\n ).length\n : rawData.total,\n }\n : undefined;\n\n const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n setSearchValue(e.target.value);\n };\n\n if (error) {\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n <div className=\"flex flex-col items-center justify-center py-12\">\n <p className=\"text-status-error-text text-lg font-semibold\">\n {tWithFallback(t, 'workspacesList.loadError', 'Error loading workspaces')}\n </p>\n <p className=\"text-text-secondary text-sm mt-2\">\n {error instanceof Error\n ? error.message\n : tWithFallback(t, 'workspacesList.unknownError', 'Unknown error')}\n </p>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n {/* Header with title + actions */}\n <div\n className=\"relative overflow-hidden rounded-card flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 px-3 py-3 sm:px-4 sm:py-4\"\n data-tour=\"workspaces-list-header\"\n >\n <AuroraBackground intensity=\"subtle\" />\n <div>\n <h1 className=\"text-2xl sm:text-3xl font-bold\">\n <GradientText>{tWithFallback(t, 'workspacesList.title', 'Workspaces')}</GradientText>\n </h1>\n <p className=\"text-sm sm:text-base text-text-secondary\">\n {tWithFallback(t, 'workspacesList.description', 'Manage and organize your workspaces')}\n </p>\n </div>\n <div className=\"flex items-center gap-2\" data-tour=\"workspaces-list-actions\">\n {/* View mode toggle */}\n <Button\n variant={viewMode === 'grid' ? 'default' : 'outline'}\n size=\"icon\"\n onClick={() => setViewMode('grid')}\n className=\"hidden sm:flex\"\n aria-label={tWithFallback(\n t,\n 'workspacesList.gridViewAriaLabel',\n 'Show workspaces in grid view'\n )}\n title={tWithFallback(t, 'workspacesList.gridViewTitle', 'Grid view')}\n >\n <Grid3X3 size={20} />\n </Button>\n <Button\n variant={viewMode === 'list' ? 'default' : 'outline'}\n size=\"icon\"\n onClick={() => setViewMode('list')}\n className=\"hidden sm:flex\"\n aria-label={tWithFallback(\n t,\n 'workspacesList.listViewAriaLabel',\n 'Show workspaces in list view'\n )}\n title={tWithFallback(t, 'workspacesList.listViewTitle', 'List view')}\n >\n <ListIcon size={20} />\n </Button>\n {/* Secondary actions collapsed into an overflow \"More\" menu */}\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"outline\" size=\"icon\" aria-label=\"More actions\" className=\"relative\">\n <MoreHorizontal size={18} />\n {pendingInvitesCount > 0 ? (\n <span className=\"absolute -top-1 -right-1 flex size-2 rounded-full bg-action-primary-bg\" />\n ) : null}\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <PermissionButton\n permission={WORKSPACE_PERMISSIONS.MEMBER_CREATE}\n deniedBehavior=\"disable\"\n deniedTooltip={tWithFallback(\n t,\n 'workspacesList.inviteDeniedTooltip',\n \"You don't have permission to invite members into the current workspace\"\n )}\n >\n <DropdownMenuItem\n onSelect={handleInviteMember}\n disabled={!currentWorkspaceId}\n className=\"gap-2\"\n >\n <UserPlus size={16} />\n {tWithFallback(t, 'workspacesList.inviteMemberButton', 'Invite Member')}\n </DropdownMenuItem>\n </PermissionButton>\n <DropdownMenuItem onSelect={handleOpenPendingInvites} className=\"gap-2\">\n <Mail size={16} />\n <span className=\"flex-1\">\n {tWithFallback(t, 'workspacesList.pendingInvitesButton', 'Pending Invites')}\n </span>\n {pendingInvitesCount > 0 ? (\n <Badge variant=\"secondary\">{pendingInvitesCount}</Badge>\n ) : null}\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n <PermissionButton\n permission={WORKSPACE_PERMISSIONS.WORKSPACE_CREATE}\n deniedBehavior=\"disable\"\n deniedTooltip={tWithFallback(\n t,\n 'workspacesList.createDeniedTooltip',\n \"You don't have permission to create workspaces\"\n )}\n >\n <Button\n onClick={handleCreateWorkspace}\n className=\"gap-2\"\n data-tour=\"workspaces-create-button\"\n >\n <Plus size={20} />\n <span className=\"hidden sm:inline\">\n {tWithFallback(t, 'workspacesList.createButton', 'Create Workspace')}\n </span>\n <span className=\"sm:hidden\">\n {tWithFallback(t, 'workspacesList.createButtonShort', 'Create')}\n </span>\n </Button>\n </PermissionButton>\n </div>\n </div>\n\n <PagePurpose>\n {tWithFallback(\n t,\n 'workspacesList.purpose',\n 'Workspaces are shared homes for a team or initiative — each one groups its own projects, members, agents and activity. Create one per team, client or product line to keep work isolated, then switch between them from the sidebar.'\n )}\n </PagePurpose>\n\n {/* Search */}\n <div className=\"relative\" data-tour=\"workspaces-search-input\">\n <Search\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary\"\n size={20}\n />\n <Input\n placeholder={tWithFallback(t, 'workspacesList.searchPlaceholder', 'Search workspaces...')}\n value={searchValue}\n onChange={handleSearchChange}\n className=\"pl-[3.25rem]\"\n />\n </div>\n\n {/* Content */}\n <div className=\"space-y-4 sm:space-y-6\" data-tour=\"workspaces-view-container\">\n {isLoading ? (\n <div className=\"grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4\">\n {Array.from({ length: 6 }).map((_, i) => (\n <div key={i} className=\"space-y-3 p-6 border border-border-seam rounded-lg\">\n <div className=\"flex items-center gap-3\">\n <Skeleton className=\"size-12 rounded-lg\" />\n <div className=\"space-y-2 flex-1\">\n <Skeleton className=\"h-4 w-3/4\" />\n <Skeleton className=\"h-3 w-1/2\" />\n </div>\n </div>\n <Skeleton className=\"h-3 w-1/3\" />\n </div>\n ))}\n </div>\n ) : data?.items.length === 0 ? (\n <IllustratedEmptyState\n illustration={workspaceFilters.search ? 'empty-search' : 'onboarding'}\n title={tWithFallback(t, 'workspacesList.emptyTitle', 'No workspaces found')}\n description={\n workspaceFilters.search\n ? tWithFallback(\n t,\n 'workspacesList.emptySearchHint',\n 'Try adjusting your search criteria'\n )\n : tWithFallback(\n t,\n 'workspacesList.emptyCreateHint',\n 'Get started by creating your first workspace'\n )\n }\n action={\n !workspaceFilters.search ? (\n <PermissionButton\n permission={WORKSPACE_PERMISSIONS.WORKSPACE_CREATE}\n deniedBehavior=\"disable\"\n deniedTooltip={tWithFallback(\n t,\n 'workspacesList.createDeniedTooltip',\n \"You don't have permission to create workspaces\"\n )}\n >\n <Button onClick={handleCreateWorkspace} className=\"gap-2\">\n <Plus size={20} />\n {tWithFallback(\n t,\n 'workspacesList.createFirstButton',\n 'Create Your First Workspace'\n )}\n </Button>\n </PermissionButton>\n ) : undefined\n }\n />\n ) : (\n <>\n {/* View */}\n {viewMode === 'list' ? (\n <div className=\"space-y-3\">\n {data?.items.map((workspace) => (\n <WorkspaceCard key={workspace.id} workspace={workspace} />\n ))}\n </div>\n ) : (\n <div className=\"grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4\">\n {data?.items.map((workspace) => (\n <WorkspaceCard key={workspace.id} workspace={workspace} />\n ))}\n </div>\n )}\n\n {/* Pagination info */}\n {data && data.total > 0 && (\n <div className=\"flex items-center justify-between text-sm text-text-secondary\">\n <p>\n {tWithFallback(t, 'workspacesList.showing', 'Showing')} {data.items.length}{' '}\n {tWithFallback(t, 'workspacesList.of', 'of')} {data.total}{' '}\n {data.total !== 1\n ? tWithFallback(t, 'workspacesList.workspaces', 'workspaces')\n : tWithFallback(t, 'workspacesList.workspace', 'workspace')}\n </p>\n {data.hasMore && (\n <p className=\"text-xs\">\n {tWithFallback(\n t,\n 'workspacesList.moreAvailable',\n 'More workspaces available - pagination coming soon'\n )}\n </p>\n )}\n </div>\n )}\n </>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"DashboardCustomizer.js","names":[],"sources":["../../../src/pages/dashboard/DashboardCustomizer.tsx"],"sourcesContent":["/**\n * DashboardCustomizer — Sheet overlay for customizing dashboard widgets.\n *\n * Allows users to toggle widgets on/off, reorder them, and save their layout.\n */\nimport { useState, useCallback, useEffect, useRef } from 'react';\nimport {\n Sheet,\n SheetContent,\n SheetHeader,\n SheetTitle,\n SheetDescription,\n SheetFooter,\n Button,\n Switch,\n Badge,\n Separator,\n} from '@burdenoff/fe-libs/ui';\nimport { ArrowUp, ArrowDown, RotateCcw, Save, Loader2, GripVertical } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { tWithFallback } from '../../utils/i18n';\nimport type { WidgetConfig } from './types';\nimport {\n WIDGET_DEFINITIONS,\n DEFAULT_DASHBOARD_WIDGETS_BY_PRODUCT,\n DEFAULT_DASHBOARD_WIDGETS,\n isWidgetAvailable,\n resolveWidgetCopy,\n} from './config';\nimport { nativeImpact } from '../../utils/nativeBridge';\nimport { reorderByDropIndex, dropPositionFromPointer, type DropPosition } from './reorder';\n\ninterface DashboardCustomizerProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n widgets: WidgetConfig[];\n onSave: (widgets: WidgetConfig[]) => Promise<void>;\n isSaving: boolean;\n productId?: string;\n}\n\nexport function DashboardCustomizer({\n open,\n onOpenChange,\n widgets,\n onSave,\n isSaving,\n productId = 'workspaces',\n}: DashboardCustomizerProps) {\n const { t } = useI18n();\n // Widget titles/descriptions live in module-scope WIDGET_DEFINITIONS, so they\n // are translated here at render time with literal keys. [BOFF-5997]\n const trLiteral = useCallback(\n (key: string, fallback: string): string => tWithFallback(t, key, fallback),\n [t]\n );\n\n const [localWidgets, setLocalWidgets] = useState<WidgetConfig[]>(widgets);\n const [hasChanges, setHasChanges] = useState(false);\n const [draggingId, setDraggingId] = useState<string | null>(null);\n const [dropTargetId, setDropTargetId] = useState<string | null>(null);\n /** Live-region announcement for screen readers after a successful reorder. */\n const [liveAnnouncement, setLiveAnnouncement] = useState('');\n const rowRefs = useRef(new Map<string, HTMLDivElement>());\n /** Last (from, to, position) applied during the current drag — used to skip\n * repeated `dragOver` firings that would otherwise churn renders. */\n const lastAppliedMoveRef = useRef<{\n fromId: string;\n toId: string;\n position: DropPosition;\n } | null>(null);\n /** Pending rAF id for coalescing `dragOver` reorders to one per frame. */\n const dragOverRafRef = useRef(0);\n\n // Sync local state when sheet opens or widgets prop changes while open\n useEffect(() => {\n if (open) {\n setLocalWidgets([...widgets]);\n setHasChanges(false);\n }\n }, [open, widgets]);\n\n const toggleWidget = useCallback((widgetId: string) => {\n setLocalWidgets((prev) =>\n prev.map((w) => (w.widgetId === widgetId ? { ...w, enabled: !w.enabled } : w))\n );\n setHasChanges(true);\n }, []);\n\n const moveWidget = useCallback((widgetId: string, direction: 'up' | 'down') => {\n setLocalWidgets((prev) => {\n const sorted = [...prev].sort((a, b) => a.order - b.order);\n const idx = sorted.findIndex((w) => w.widgetId === widgetId);\n if (idx < 0) return prev;\n\n const swapIdx = direction === 'up' ? idx - 1 : idx + 1;\n if (swapIdx < 0 || swapIdx >= sorted.length) return prev;\n\n // Swap orders\n const tempOrder = sorted[idx].order;\n sorted[idx] = { ...sorted[idx], order: sorted[swapIdx].order };\n sorted[swapIdx] = { ...sorted[swapIdx], order: tempOrder };\n\n return sorted;\n });\n setHasChanges(true);\n }, []);\n\n const resetToDefaults = useCallback(() => {\n const defaults = DEFAULT_DASHBOARD_WIDGETS_BY_PRODUCT[productId] ?? DEFAULT_DASHBOARD_WIDGETS;\n setLocalWidgets([...defaults]);\n setHasChanges(true);\n }, [productId]);\n\n // ─── Drag-and-drop (HTML5 native) ──────────────────────────────────────────\n // The 6-dot icon next to each widget is the drag handle. Up/down arrow\n // buttons remain the keyboard-accessible reorder path. [BOFF-7219]\n const setRowRef = useCallback(\n (widgetId: string) => (el: HTMLDivElement | null) => {\n if (el) rowRefs.current.set(widgetId, el);\n else rowRefs.current.delete(widgetId);\n },\n []\n );\n\n const handleDragStart = useCallback(\n (widgetId: string, event: React.DragEvent<HTMLSpanElement>) => {\n setDraggingId(widgetId);\n lastAppliedMoveRef.current = null;\n cancelAnimationFrame(dragOverRafRef.current);\n event.dataTransfer.effectAllowed = 'move';\n // Firefox refuses to fire drag events without a setData payload.\n event.dataTransfer.setData('text/plain', widgetId);\n },\n []\n );\n\n const handleDragOver = useCallback(\n (widgetId: string, event: React.DragEvent<HTMLDivElement>) => {\n if (!draggingId || draggingId === widgetId) return;\n event.preventDefault();\n event.dataTransfer.dropEffect = 'move';\n setDropTargetId(widgetId);\n\n // Coalesce the expensive reorder work to once per animation frame.\n // `dragOver` fires at ~60 FPS even when the pointer is stationary;\n // without this guard every firing triggers a re-sort + re-render.\n cancelAnimationFrame(dragOverRafRef.current);\n dragOverRafRef.current = requestAnimationFrame(() => {\n const row = rowRefs.current.get(widgetId);\n if (!row) return;\n const position = dropPositionFromPointer(event.clientY, row.getBoundingClientRect());\n const last = lastAppliedMoveRef.current;\n if (\n last &&\n last.fromId === draggingId &&\n last.toId === widgetId &&\n last.position === position\n ) {\n return; // same move as last firing — skip to avoid re-render churn\n }\n lastAppliedMoveRef.current = { fromId: draggingId, toId: widgetId, position };\n setLocalWidgets((prev) => reorderByDropIndex(prev, draggingId, widgetId, position));\n setHasChanges(true);\n });\n },\n [draggingId]\n );\n\n const handleDragLeave = useCallback(\n (widgetId: string, event: React.DragEvent<HTMLDivElement>) => {\n // Only clear the drop target when the pointer truly leaves the row\n // (entering a child element bubbles dragleave on the parent).\n if (event.currentTarget.contains(event.relatedTarget as Node | null)) return;\n if (dropTargetId === widgetId) setDropTargetId(null);\n },\n [dropTargetId]\n );\n\n const handleDrop = useCallback((widgetId: string, event: React.DragEvent<HTMLDivElement>) => {\n event.preventDefault();\n if (event.dataTransfer) event.dataTransfer.dropEffect = 'move';\n cancelAnimationFrame(dragOverRafRef.current);\n setDraggingId(null);\n setDropTargetId(null);\n }, []);\n\n const handleDragEnd = useCallback(() => {\n cancelAnimationFrame(dragOverRafRef.current);\n // Announce the final position of the dragged widget for screen-reader\n // users. aria-live=\"assertive\" reads the new text immediately.\n if (draggingId) {\n const finalWidgets = [...localWidgets].sort((a, b) => a.order - b.order);\n const finalIdx = finalWidgets.findIndex((w) => w.widgetId === draggingId);\n const definition = WIDGET_DEFINITIONS[draggingId];\n const title = definition ? resolveWidgetCopy(trLiteral, definition).title : draggingId;\n if (finalIdx >= 0) {\n setLiveAnnouncement(\n tWithFallback(\n t,\n 'dashboardCustomizer.reorderAnnouncement',\n `${title} moved to position ${finalIdx + 1} of ${finalWidgets.length}`\n )\n );\n }\n }\n setDraggingId(null);\n setDropTargetId(null);\n lastAppliedMoveRef.current = null;\n }, [draggingId, localWidgets, trLiteral, t]);\n\n const handleSave = useCallback(async () => {\n await onSave(localWidgets);\n setHasChanges(false);\n onOpenChange(false);\n }, [localWidgets, onSave, onOpenChange]);\n\n const sorted = [...localWidgets].sort((a, b) => a.order - b.order);\n\n return (\n <Sheet open={open} onOpenChange={onOpenChange}>\n <SheetContent className=\"w-full sm:max-w-md overflow-y-auto\" side=\"right\">\n <SheetHeader>\n <SheetTitle>\n {tWithFallback(t, 'dashboardCustomizer.title', 'Customize Dashboard')}\n </SheetTitle>\n <SheetDescription>\n {tWithFallback(\n t,\n 'dashboardCustomizer.description',\n 'Toggle widgets on or off and reorder them to personalize your dashboard.'\n )}\n </SheetDescription>\n </SheetHeader>\n\n <div\n role=\"listbox\"\n aria-label={tWithFallback(t, 'dashboardCustomizer.listLabel', 'Dashboard widgets')}\n className=\"py-4 space-y-1\"\n >\n {/* Live region for screen-reader announcements after a successful\n reorder. `aria-live=\"assertive\"` interrupts the user to make\n the new position obvious; the region itself is visually\n hidden via `sr-only`. */}\n <div aria-live=\"assertive\" className=\"sr-only\">\n {liveAnnouncement}\n </div>\n {sorted.map((widget, idx) => {\n const definition = WIDGET_DEFINITIONS[widget.widgetId];\n if (!definition) return null;\n if (!isWidgetAvailable(definition, productId)) return null;\n\n const isDragging = draggingId === widget.widgetId;\n const isDropTarget = dropTargetId === widget.widgetId && draggingId !== null;\n const widgetTitle = resolveWidgetCopy(trLiteral, definition).title;\n\n return (\n <div\n key={widget.widgetId}\n ref={setRowRef(widget.widgetId)}\n role=\"option\"\n aria-roledescription={tWithFallback(\n t,\n 'dashboardCustomizer.sortableItem',\n 'sortable item'\n )}\n aria-selected={isDragging}\n aria-label={tWithFallback(\n t,\n 'dashboardCustomizer.widgetRow',\n `Widget: ${widgetTitle}. Use the arrow buttons to reorder.`\n )}\n onDragOver={(e) => handleDragOver(widget.widgetId, e)}\n onDragLeave={(e) => handleDragLeave(widget.widgetId, e)}\n onDrop={(e) => handleDrop(widget.widgetId, e)}\n data-widget-id={widget.widgetId}\n className={`flex items-center gap-3 p-3 rounded-lg border transition-colors ${\n widget.enabled\n ? 'border-border-default bg-bg-surface'\n : 'border-transparent bg-bg-sunken/30 opacity-60'\n } ${isDragging ? 'opacity-50' : ''} ${\n isDropTarget ? 'ring-2 ring-border-strong border-transparent' : ''\n }`}\n >\n <span\n draggable\n onDragStart={(e) => handleDragStart(widget.widgetId, e)}\n onDragEnd={handleDragEnd}\n aria-label={tWithFallback(\n t,\n 'dashboardCustomizer.dragHandle',\n `Drag handle for ${widgetTitle}`\n )}\n className=\"cursor-grab active:cursor-grabbing touch-none select-none\"\n >\n <GripVertical aria-hidden className=\"size-4 text-text-secondary shrink-0\" />\n </span>\n\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm font-medium text-text-primary\">{widgetTitle}</span>\n <Badge variant=\"outline\" className=\"text-[10px]\">\n {definition.defaultSize}\n </Badge>\n </div>\n <p className=\"text-xs text-text-secondary truncate\">\n {resolveWidgetCopy(trLiteral, definition).description}\n </p>\n </div>\n\n <div className=\"flex items-center gap-1 shrink-0\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"size-7 p-0\"\n disabled={idx === 0}\n onClick={() => moveWidget(widget.widgetId, 'up')}\n aria-label={tWithFallback(\n t,\n 'dashboardCustomizer.moveUp',\n `Move ${widgetTitle} up`\n )}\n >\n <ArrowUp className=\"size-3\" />\n </Button>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"size-7 p-0\"\n disabled={idx === sorted.length - 1}\n onClick={() => moveWidget(widget.widgetId, 'down')}\n aria-label={tWithFallback(\n t,\n 'dashboardCustomizer.moveDown',\n `Move ${widgetTitle} down`\n )}\n >\n <ArrowDown className=\"size-3\" />\n </Button>\n <Switch\n checked={widget.enabled}\n onCheckedChange={() => {\n void nativeImpact('medium');\n toggleWidget(widget.widgetId);\n }}\n disabled={!definition.removable}\n className=\"ml-1\"\n />\n </div>\n </div>\n );\n })}\n </div>\n\n <Separator />\n\n <SheetFooter className=\"flex-row gap-2 pt-4\">\n <Button variant=\"outline\" size=\"sm\" onClick={resetToDefaults} className=\"flex-1\">\n <RotateCcw className=\"size-3 mr-1.5\" />\n {tWithFallback(t, 'dashboardCustomizer.resetToDefaults', 'Reset to defaults')}\n </Button>\n <Button\n size=\"sm\"\n onClick={handleSave}\n disabled={!hasChanges || isSaving}\n className=\"flex-1\"\n >\n {isSaving ? (\n <Loader2 className=\"size-3 mr-1.5 animate-spin\" />\n ) : (\n <Save className=\"size-3 mr-1.5\" />\n )}\n {tWithFallback(t, 'dashboardCustomizer.saveLayout', 'Save layout')}\n </Button>\n </SheetFooter>\n </SheetContent>\n </Sheet>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"DashboardGrid.js","names":[],"sources":["../../../src/pages/dashboard/DashboardGrid.tsx"],"sourcesContent":["/**\n * DashboardGrid — Renders the configured widgets in a responsive grid.\n */\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { tWithFallback } from '../../utils/i18n';\nimport type { WidgetConfig, WidgetProps } from './types';\nimport { isWidgetAvailable, WIDGET_DEFINITIONS } from './config';\nimport { getColSpan } from './WidgetWrapper';\n\n// Widget components\nimport { WelcomeWidget } from './widgets/WelcomeWidget';\nimport { QuickActionsWidget } from './widgets/QuickActionsWidget';\nimport { WorkspacesWidget } from './widgets/WorkspacesWidget';\nimport { ProjectsWidget } from './widgets/ProjectsWidget';\nimport { RecentActivityWidget } from './widgets/RecentActivityWidget';\nimport { UpcomingEventsWidget } from './widgets/UpcomingEventsWidget';\nimport { WorkflowStatsWidget } from './widgets/WorkflowStatsWidget';\nimport { TagCloudWidget } from './widgets/TagCloudWidget';\nimport { StorageWidget } from './widgets/StorageWidget';\nimport { GroupsWidget } from './widgets/GroupsWidget';\nimport { MembersWidget } from './widgets/MembersWidget';\nimport { TipsWidget } from './widgets/TipsWidget';\nimport { VibesWidget } from './widgets/VibesWidget';\nimport { SessionsWidget } from './widgets/SessionsWidget';\nimport { AgentsWidget } from './widgets/AgentsWidget';\nimport { WorkflowsOverviewWidget } from './widgets/WorkflowsOverviewWidget';\nimport { WorkflowRunsWidget } from './widgets/WorkflowRunsWidget';\n\n// Widget component registry\nconst WIDGET_COMPONENTS: Record<string, React.FC<WidgetProps>> = {\n welcome: WelcomeWidget,\n quickActions: QuickActionsWidget,\n workspaces: WorkspacesWidget,\n projects: ProjectsWidget,\n activity: RecentActivityWidget,\n calendar: UpcomingEventsWidget,\n workflows: WorkflowStatsWidget,\n tags: TagCloudWidget,\n storage: StorageWidget,\n groups: GroupsWidget,\n members: MembersWidget,\n tips: TipsWidget,\n vibes: VibesWidget,\n sessions: SessionsWidget,\n agents: AgentsWidget,\n workflowsOverview: WorkflowsOverviewWidget,\n workflowRuns: WorkflowRunsWidget,\n};\n\ninterface DashboardGridProps extends WidgetProps {\n widgets: WidgetConfig[];\n}\n\nexport function DashboardGrid({ widgets, productId, ...widgetProps }: DashboardGridProps) {\n const { t } = useI18n();\n\n const enabledWidgets = widgets\n .filter((w) => w.enabled)\n .filter((w) => {\n const def = WIDGET_DEFINITIONS[w.widgetId];\n if (!def) return false;\n return isWidgetAvailable(def, productId);\n })\n .sort((a, b) => a.order - b.order);\n\n if (enabledWidgets.length === 0) {\n return (\n <div className=\"text-center py-12\">\n <p className=\"text-lg text-text-secondary\">\n {tWithFallback(t, 'dashboardGrid.noWidgetsEnabled', 'No widgets enabled')}\n </p>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tWithFallback(\n t,\n 'dashboardGrid.noWidgetsHint',\n 'Click \"Customize\" to add widgets to your dashboard'\n )}\n </p>\n </div>\n );\n }\n\n return (\n <MotionList className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\" stagger={0.06}>\n {enabledWidgets.map((widgetConfig) => {\n const Component = WIDGET_COMPONENTS[widgetConfig.widgetId];\n if (!Component) return null;\n\n return (\n <MotionItem\n key={widgetConfig.widgetId}\n className={getColSpan(widgetConfig.size)}\n variant=\"fadeUp\"\n hoverable\n >\n <Component productId={productId} {...widgetProps} />\n </MotionItem>\n );\n })}\n </MotionList>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA8BA,IAAM,IAA2D;CAC/D,SAAS;CACT,cAAc;CACd,YAAY;CACZ,UAAU;CACV,UAAU;CACV,UAAU;CACV,WAAW;CACX,MAAM;CACN,SAAS;CACT,QAAQ;CACR,SAAS;CACT,MAAM;CACN,OAAO;CACP,UAAU;CACV,QAAQ;CACR,mBAAmB;CACnB,cAAc;CACf;AAMD,SAAgB,EAAc,EAAE,YAAS,cAAW,GAAG,KAAmC;CACxF,IAAM,EAAE,SAAM,GAAS,EAEjB,IAAiB,EACpB,QAAQ,MAAM,EAAE,QAAQ,CACxB,QAAQ,MAAM;EACb,IAAM,IAAM,EAAmB,EAAE;AAEjC,SADK,IACE,EAAkB,GAAK,EAAU,GADvB;GAEjB,CACD,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM;AAmBpC,QAjBI,EAAe,WAAW,IAE1B,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,KAAD;GAAG,WAAU;aACV,EAAc,GAAG,kCAAkC,qBAAqB;GACvE,CAAA,EACJ,kBAAC,KAAD;GAAG,WAAU;aACV,EACC,GACA,+BACA,uDACD;GACC,CAAA,CACA;MAKR,kBAAC,GAAD;EAAY,WAAU;EAAuD,SAAS;YACnF,EAAe,KAAK,MAAiB;GACpC,IAAM,IAAY,EAAkB,EAAa;AAGjD,UAFK,IAGH,kBAAC,GAAD;IAEE,WAAW,EAAW,EAAa,KAAK;IACxC,SAAQ;IACR,WAAA;cAEA,kBAAC,GAAD;KAAsB;KAAW,GAAI;KAAe,CAAA;IACzC,EANN,EAAa,SAMP,GAVQ;IAYvB;EACS,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"DashboardPage.js","names":[],"sources":["../../../src/pages/dashboard/DashboardPage.tsx"],"sourcesContent":["/**\n * DashboardPage — Comprehensive workspace dashboard.\n *\n * This is the main dashboard component exported from microfe-workspaces.\n * It fetches data from both workspace and global gateways, manages widget\n * layout preferences, and renders a customizable grid of widgets.\n *\n * Props:\n * - navigate: Shell navigation function\n * - authToken: Current user's auth token\n * - currentUser: Basic user info from shell\n *\n * Data Flow:\n * 1. Two parallel queries: workspace gateway + global gateway\n * 2. Global query returns user preferences (dashboard layout)\n * 3. Widget layout falls back to defaults if no preferences saved\n * 4. Data is passed as props to individual widgets\n */\nimport { useState, useMemo, useCallback } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { Button, PagePurpose, AuroraBackground, GradientText } from '@burdenoff/fe-libs/ui';\nimport { PageHeader } from '@burdenoff/fe-libs/chrome';\nimport {\n Settings2,\n RefreshCw,\n Loader2,\n AlertCircle,\n Building2,\n Sparkles,\n GitBranch,\n type LucideIcon,\n} from 'lucide-react';\nimport {\n useWorkspaceDashboardData,\n useGlobalDashboardData,\n useProjectsDashboardData,\n useVibecontrolsDashboardData,\n} from './hooks/useDashboardData';\nimport { useUpdateDashboardPreferences } from './hooks/useDashboardPreferences';\nimport { DashboardGrid } from './DashboardGrid';\nimport { DashboardCustomizer } from './DashboardCustomizer';\nimport { getEffectiveWidgets } from './config';\nimport type { WidgetConfig } from './types';\nimport { useWorkspacesPageView } from '../../hooks/useWorkspacesPageView';\n\ninterface ProductHeroCopy {\n noun: string;\n subtitle: string;\n purpose: string;\n Icon: LucideIcon;\n}\n\n/** Per-product hero heading/subtitle/purpose copy — keeps the dashboard from\n * reading as generic boilerplate for products layered on the shared page. */\nconst PRODUCT_HERO: Record<string, ProductHeroCopy> = {\n workspaces: {\n noun: 'Workspaces',\n subtitle: 'Your workspace overview at a glance',\n purpose:\n 'This is your home base — a single glance across everything that matters: your workspaces and projects, recent activity, upcoming events and live status. Use Customize to choose which cards appear and how they are arranged, and click any card to dive into the full view.',\n Icon: Building2,\n },\n vibecontrols: {\n noun: 'VibeControls',\n subtitle: 'Your workspace at a glance',\n purpose:\n 'This is your home base for this workspace — check on recent vibes, active sessions, and your agent fleet, then jump straight into whatever needs you. Use Customize to choose which cards appear and how they are arranged.',\n Icon: Sparkles,\n },\n fluidgrids: {\n noun: 'FluidGrids',\n subtitle: 'Your workflow automation at a glance',\n purpose:\n 'This is your home base — track workflow health, recent runs, and team activity, then jump straight into building or debugging. Use Customize to choose which cards appear and how they are arranged.',\n Icon: GitBranch,\n },\n};\n\n/**\n * Resolve the per-product hero copy through i18n with **literal** keys.\n *\n * These three strings used to build their key by interpolating productId into\n * a template literal. The key extractor\n * (fluidgrids-app/scripts/extract-i18n-keys.ts) only collects string literals,\n * so an interpolated key is never extracted and never seeded — it can only ever\n * render its English fallback, in every locale, forever. Spelling each product\n * out gives the extractor something to find. [BOFF-5997]\n */\nfunction resolveHeroCopy(\n tr: (key: string, fallback: string) => string,\n productId: string,\n hero: ProductHeroCopy\n): { noun: string; subtitle: string; purpose: string } {\n switch (productId) {\n case 'vibecontrols':\n return {\n noun: tr('dashboard.titleNoun.vibecontrols', hero.noun),\n subtitle: tr('dashboard.subtitle.vibecontrols', hero.subtitle),\n purpose: tr('dashboard.purpose.vibecontrols', hero.purpose),\n };\n case 'fluidgrids':\n return {\n noun: tr('dashboard.titleNoun.fluidgrids', hero.noun),\n subtitle: tr('dashboard.subtitle.fluidgrids', hero.subtitle),\n purpose: tr('dashboard.purpose.fluidgrids', hero.purpose),\n };\n default:\n return {\n noun: tr('dashboard.titleNoun.workspaces', hero.noun),\n subtitle: tr('dashboard.subtitle.workspaces', hero.subtitle),\n purpose: tr('dashboard.purpose.workspaces', hero.purpose),\n };\n }\n}\n\ninterface DashboardPageProps {\n navigate: (path: string) => void;\n authToken?: string | null;\n currentUser?: {\n id: string;\n email: string;\n firstName?: string;\n lastName?: string;\n name?: string;\n } | null;\n /**\n * Host product identifier ('workspaces', 'vibecontrols', etc.). Drives\n * which widgets appear in the registry + default layout. Defaults to\n * 'workspaces' for legacy callers.\n */\n productId?: string;\n}\n\nexport function DashboardPage({\n navigate,\n authToken,\n currentUser: _currentUser,\n productId = 'workspaces',\n}: DashboardPageProps) {\n useWorkspacesPageView('workspaces.dashboard');\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n const [customizerOpen, setCustomizerOpen] = useState(false);\n const hero = PRODUCT_HERO[productId] ?? PRODUCT_HERO.workspaces;\n const heroCopy = resolveHeroCopy(tr, productId, hero);\n const HeroIcon = hero.Icon;\n\n // ─── Data Fetching ────────────────────────────────────────────────────────\n const {\n data: workspaceData,\n isLoading: wsLoading,\n error: wsError,\n refetch: refetchWs,\n } = useWorkspaceDashboardData(authToken);\n\n const {\n data: globalData,\n isLoading: globalLoading,\n error: globalError,\n refetch: refetchGlobal,\n } = useGlobalDashboardData(authToken);\n\n const {\n data: projectsData,\n isLoading: projectsLoading,\n refetch: refetchProjects,\n } = useProjectsDashboardData(authToken);\n\n const isVibecontrols = productId === 'vibecontrols';\n const {\n data: vibecontrolsData,\n isLoading: vibecontrolsLoading,\n refetch: refetchVibecontrols,\n } = useVibecontrolsDashboardData(authToken, isVibecontrols);\n\n const updatePrefs = useUpdateDashboardPreferences(authToken);\n\n const isLoading = wsLoading || globalLoading || projectsLoading || vibecontrolsLoading;\n\n // ─── Merge data ───────────────────────────────────────────────────────────\n // Workspace/org stats come from the global tenant service (globalData),\n // while workspace-scoped features (activity, calendar, tags, etc.) come\n // from the workspace gateway (workspaceData). Merge them so widgets\n // reading workspaceData.workspaces see the real tenant data.\n const mergedWorkspaceData = useMemo(() => {\n if (!workspaceData && !globalData && !projectsData && !vibecontrolsData) return null;\n return {\n ...(workspaceData ?? {\n workspaces: { items: [], total: 0 },\n projects: { items: [], total: 0 },\n recentActivities: [],\n upcomingEvents: [],\n tags: { items: [], total: 0 },\n groups: { items: [], totalCount: 0 },\n workflowStats: null,\n storageUsage: null,\n }),\n // Override with real workspace/org data from the global gateway\n workspaces: globalData?.workspaces ?? { items: [], total: 0 },\n // Override with real project data from the separate projects query\n // (fetched independently to avoid being poisoned by other subgraph errors)\n projects: projectsData ?? { items: [], total: 0 },\n // VibeControls-only data; undefined for other products\n recentVibes: vibecontrolsData?.recentVibes,\n activeSessions: vibecontrolsData?.activeSessions,\n agentFleet: vibecontrolsData?.agentFleet ?? null,\n };\n }, [workspaceData, globalData, projectsData, vibecontrolsData]);\n\n // ─── Widget Configuration ─────────────────────────────────────────────────\n const savedPrefs = globalData?.dashboardPreferences;\n const widgets = useMemo(\n () => getEffectiveWidgets(savedPrefs?.widgets ?? null, productId),\n [savedPrefs, productId]\n );\n\n // ─── Handlers ─────────────────────────────────────────────────────────────\n const handleRefresh = useCallback(() => {\n refetchWs();\n refetchGlobal();\n refetchProjects();\n if (isVibecontrols) refetchVibecontrols();\n }, [refetchWs, refetchGlobal, refetchProjects, refetchVibecontrols, isVibecontrols]);\n\n const handleSaveWidgets = useCallback(\n async (newWidgets: WidgetConfig[]) => {\n await updatePrefs.mutateAsync(newWidgets);\n },\n [updatePrefs]\n );\n\n // ─── Error Banner ─────────────────────────────────────────────────────────\n const hasError = wsError || globalError;\n\n return (\n <div className=\"min-h-[calc(100vh-8rem)] p-4 md:p-6 bg-bg-canvas\" data-tour=\"main-content\">\n {/* Header */}\n <div className=\"relative overflow-hidden rounded-card -mx-1 px-1 pt-1\">\n <AuroraBackground intensity=\"subtle\" />\n <PageHeader\n icon={<HeroIcon className=\"size-6\" />}\n title={\n <>\n <GradientText>{heroCopy.noun}</GradientText>{' '}\n {tr('dashboard.titleSuffix', 'Dashboard')}\n </>\n }\n description={heroCopy.subtitle}\n actions={\n <div className=\"flex flex-wrap items-center gap-2\" data-tour=\"quick-actions\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={handleRefresh}\n disabled={isLoading}\n className=\"text-xs\"\n >\n {isLoading ? (\n <Loader2 className=\"size-3.5 sm:mr-1.5 animate-spin\" />\n ) : (\n <RefreshCw className=\"size-3.5 sm:mr-1.5\" />\n )}\n <span className=\"hidden sm:inline\">{tr('dashboard.refresh', 'Refresh')}</span>\n </Button>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setCustomizerOpen(true)}\n className=\"text-xs\"\n >\n <Settings2 className=\"size-3.5 sm:mr-1.5\" />\n <span className=\"hidden sm:inline\">{tr('dashboard.customize', 'Customize')}</span>\n </Button>\n </div>\n }\n />\n </div>\n\n <PagePurpose icon={<HeroIcon className=\"size-4\" />} className=\"mb-6 -mt-2\">\n {heroCopy.purpose}\n </PagePurpose>\n\n {/* Error Banner */}\n {hasError && (\n <div className=\"mb-4 p-3 bg-status-error-bg/10 border border-status-error-border/20 rounded-lg flex items-center gap-2\">\n <AlertCircle className=\"size-4 text-status-error-text shrink-0\" />\n <p className=\"text-sm text-status-error-text\">\n {tr('dashboard.error.partialData', 'Some data may be unavailable.')}{' '}\n {wsError ? tr('dashboard.error.workspaceDown', 'Workspace services may be down.') : ''}{' '}\n {globalError ? tr('dashboard.error.globalDown', 'Global services may be down.') : ''}{' '}\n <button\n type=\"button\"\n className=\"underline font-medium hover:text-status-error-text/80\"\n onClick={handleRefresh}\n >\n {tr('dashboard.error.tryRefreshing', 'Try refreshing')}\n </button>\n </p>\n </div>\n )}\n\n {/* Widget Grid */}\n {/* Merge workspace/org data from the global query into workspaceData\n so widgets that read workspaceData.workspaces see the real data\n from the global tenant service. */}\n <DashboardGrid\n widgets={widgets}\n workspaceData={mergedWorkspaceData}\n globalData={globalData ?? null}\n isLoading={isLoading}\n navigate={navigate}\n productId={productId}\n />\n\n {/* Customizer */}\n <DashboardCustomizer\n open={customizerOpen}\n onOpenChange={setCustomizerOpen}\n widgets={widgets}\n onSave={handleSaveWidgets}\n isSaving={updatePrefs.isPending}\n productId={productId}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"WidgetWrapper.js","names":[],"sources":["../../../src/pages/dashboard/WidgetWrapper.tsx"],"sourcesContent":["/**\n * WidgetWrapper — Common wrapper for all dashboard widgets.\n *\n * Provides consistent card styling, loading skeleton, error state,\n * and unavailable state.\n */\nimport { type ReactNode } from 'react';\nimport { GlassCard, CardContent, CardHeader, CardTitle } from '@burdenoff/fe-libs/ui';\nimport { Skeleton } from '@burdenoff/fe-libs/ui';\nimport { AlertCircle, WifiOff } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { tWithFallback } from '../../utils/i18n';\nimport type { WidgetSize } from './types';\n\ninterface WidgetWrapperProps {\n title: string;\n icon?: ReactNode;\n size: WidgetSize;\n isLoading?: boolean;\n error?: string | null;\n unavailable?: boolean;\n headerAction?: ReactNode;\n children: ReactNode;\n className?: string;\n}\n\nexport function getColSpan(size: WidgetSize): string {\n switch (size) {\n case 'large':\n return 'col-span-1 sm:col-span-2 lg:col-span-3';\n case 'medium':\n return 'col-span-1 sm:col-span-2 lg:col-span-2 xl:col-span-2 2xl:col-span-1';\n case 'small':\n return 'col-span-1';\n default:\n return 'col-span-1';\n }\n}\n\nfunction LoadingSkeleton() {\n return (\n <div className=\"space-y-3 p-1\">\n <Skeleton className=\"h-4 w-3/4\" />\n <Skeleton className=\"h-4 w-1/2\" />\n <Skeleton className=\"h-20 w-full\" />\n <Skeleton className=\"h-4 w-2/3\" />\n </div>\n );\n}\n\nfunction ErrorState({ message }: { message: string }) {\n return (\n <div className=\"flex flex-col items-center justify-center py-6 text-center\">\n <div className=\"rounded-full bg-status-error-bg/10 p-2 mb-2\">\n <AlertCircle className=\"size-5 text-status-error-text\" />\n </div>\n <p className=\"text-sm text-text-secondary\">{message}</p>\n </div>\n );\n}\n\nfunction UnavailableState() {\n const { t } = useI18n();\n\n return (\n <div className=\"flex flex-col items-center justify-center py-6 text-center\">\n <div className=\"rounded-full bg-bg-sunken p-2 mb-2\">\n <WifiOff className=\"size-5 text-text-secondary\" />\n </div>\n <p className=\"text-sm text-text-secondary\">\n {tWithFallback(t, 'widgetWrapper.serviceUnavailable', 'Service unavailable')}\n </p>\n </div>\n );\n}\n\nexport function WidgetWrapper({\n title,\n icon,\n size,\n isLoading,\n error,\n unavailable,\n headerAction,\n children,\n className = '',\n}: WidgetWrapperProps) {\n const colSpan = getColSpan(size);\n\n return (\n <GlassCard\n treatment=\"insight\"\n className={`h-full ${colSpan} border-border-seam bg-bg-surface ${className}`}\n >\n <CardHeader className=\"flex flex-row items-center justify-between pb-2 gap-y-0\">\n <CardTitle className=\"text-sm font-semibold text-text-primary flex items-center gap-2\">\n {icon}\n {title}\n </CardTitle>\n {headerAction}\n </CardHeader>\n <CardContent className=\"pt-0\">\n {isLoading ? (\n <LoadingSkeleton />\n ) : error ? (\n <ErrorState message={error} />\n ) : unavailable ? (\n <UnavailableState />\n ) : (\n children\n )}\n </CardContent>\n </GlassCard>\n );\n}\n"],"mappings":";;;;;;AA0BA,SAAgB,EAAW,GAA0B;AACnD,SAAQ,GAAR;EACE,KAAK,QACH,QAAO;EACT,KAAK,SACH,QAAO;EACT,KAAK,QACH,QAAO;EACT,QACE,QAAO;;;AAIb,SAAS,IAAkB;AACzB,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD,EAAU,WAAU,aAAc,CAAA;GAClC,kBAAC,GAAD,EAAU,WAAU,aAAc,CAAA;GAClC,kBAAC,GAAD,EAAU,WAAU,eAAgB,CAAA;GACpC,kBAAC,GAAD,EAAU,WAAU,aAAc,CAAA;GAC9B;;;AAIV,SAAS,EAAW,EAAE,cAAgC;AACpD,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,GAAD,EAAa,WAAU,iCAAkC,CAAA;GACrD,CAAA,EACN,kBAAC,KAAD;GAAG,WAAU;aAA+B;GAAY,CAAA,CACpD;;;AAIV,SAAS,IAAmB;CAC1B,IAAM,EAAE,MAAM,GAAS;AAEvB,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,GAAD,EAAS,WAAU,8BAA+B,CAAA;GAC9C,CAAA,EACN,kBAAC,KAAD;GAAG,WAAU;aACV,EAAc,GAAG,oCAAoC,sBAAsB;GAC1E,CAAA,CACA;;;AAIV,SAAgB,EAAc,EAC5B,UACA,SACA,SACA,cACA,UACA,gBACA,iBACA,aACA,eAAY,MACS;AAGrB,QACE,kBAAC,GAAD;EACE,WAAU;EACV,WAAW,UALC,EAAW,EAAK,CAKC,oCAAoC;YAFnE,CAIE,kBAAC,GAAD;GAAY,WAAU;aAAtB,CACE,kBAAC,GAAD;IAAW,WAAU;cAArB,CACG,GACA,EACS;OACX,EACU;MACb,kBAAC,GAAD;GAAa,WAAU;aACpB,IACC,kBAAC,GAAD,EAAmB,CAAA,GACjB,IACF,kBAAC,GAAD,EAAY,SAAS,GAAS,CAAA,GAC5B,IACF,kBAAC,GAAD,EAAoB,CAAA,GAEpB;GAEU,CAAA,CACJ"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","names":[],"sources":["../../../src/pages/dashboard/config.ts"],"sourcesContent":["/**\n * Dashboard Widget Registry & Default Configuration\n *\n * Product-aware. Hosts pass `productId` to filter widgets relevant for that\n * product. `availableInProducts` on each definition gates registry visibility;\n * `DEFAULT_DASHBOARD_WIDGETS_BY_PRODUCT` controls the default visible/order\n * layout for first-time users.\n */\nimport type { WidgetConfig, WidgetDefinition } from './types';\n\nexport const DEFAULT_PRODUCT_ID = 'workspaces';\n\n// ─── Widget Definitions ───────────────────────────────────────────────────────\n\nexport const WIDGET_DEFINITIONS: Record<string, WidgetDefinition> = {\n welcome: {\n id: 'welcome',\n title: 'Welcome',\n description: 'Greeting and overview statistics',\n icon: 'LayoutDashboard',\n defaultSize: 'large',\n removable: false,\n dataSource: 'global',\n },\n quickActions: {\n id: 'quickActions',\n title: 'Quick Actions',\n description: 'Frequently used actions and shortcuts',\n icon: 'Zap',\n defaultSize: 'medium',\n removable: true,\n dataSource: 'static',\n },\n workspaces: {\n id: 'workspaces',\n title: 'Workspaces',\n description: 'Your workspaces overview and quick access',\n icon: 'Building2',\n defaultSize: 'medium',\n removable: true,\n dataSource: 'workspace',\n },\n projects: {\n id: 'projects',\n title: 'Projects',\n description: 'Recent projects and status breakdown',\n icon: 'FolderKanban',\n defaultSize: 'medium',\n removable: true,\n dataSource: 'workspace',\n },\n activity: {\n id: 'activity',\n title: 'Recent Activity',\n description: 'Timeline of recent activity across modules',\n icon: 'Activity',\n defaultSize: 'large',\n removable: true,\n dataSource: 'workspace',\n },\n calendar: {\n id: 'calendar',\n title: 'Upcoming Events',\n description: 'Next upcoming calendar events',\n icon: 'Calendar',\n defaultSize: 'medium',\n removable: true,\n dataSource: 'workspace',\n availableInProducts: ['workspaces'],\n },\n workflows: {\n id: 'workflows',\n title: 'Workflows',\n description: 'Active workflow stats and recent runs',\n icon: 'GitBranch',\n defaultSize: 'medium',\n removable: true,\n dataSource: 'workspace',\n availableInProducts: ['workspaces'],\n },\n tags: {\n id: 'tags',\n title: 'Tags',\n description: 'Popular tags across your workspace',\n icon: 'Tags',\n defaultSize: 'small',\n removable: true,\n dataSource: 'workspace',\n },\n storage: {\n id: 'storage',\n title: 'Storage',\n description: 'Storage usage and file statistics',\n icon: 'HardDrive',\n defaultSize: 'small',\n removable: true,\n dataSource: 'workspace',\n },\n groups: {\n id: 'groups',\n title: 'Groups',\n description: 'Your groups and teams',\n icon: 'Users',\n defaultSize: 'small',\n removable: true,\n dataSource: 'workspace',\n },\n members: {\n id: 'members',\n title: 'Members',\n description: 'Workspace member overview',\n icon: 'UserPlus',\n defaultSize: 'small',\n removable: true,\n dataSource: 'workspace',\n },\n tips: {\n id: 'tips',\n title: 'Tips & Resources',\n description: 'Getting started tips and documentation links',\n icon: 'Lightbulb',\n defaultSize: 'small',\n removable: true,\n dataSource: 'static',\n },\n // ── VibeControls-specific widgets ───────────────────────────────────────────\n vibes: {\n id: 'vibes',\n title: 'Recent Vibes',\n description: 'Your most recently active development vibes',\n icon: 'Sparkles',\n defaultSize: 'medium',\n removable: true,\n dataSource: 'workspace',\n availableInProducts: ['vibecontrols'],\n },\n sessions: {\n id: 'sessions',\n title: 'Active Sessions',\n description: 'Running terminal and agent sessions',\n icon: 'Terminal',\n defaultSize: 'medium',\n removable: true,\n dataSource: 'workspace',\n availableInProducts: ['vibecontrols'],\n },\n agents: {\n id: 'agents',\n title: 'Agent Fleet',\n description: 'Online, offline, and degraded agents at a glance',\n icon: 'Bot',\n defaultSize: 'small',\n removable: true,\n dataSource: 'workspace',\n availableInProducts: ['vibecontrols'],\n },\n // ── FluidGrids-specific widgets ─────────────────────────────────────────────\n workflowsOverview: {\n id: 'workflowsOverview',\n title: 'Workflows',\n description: 'Active/archived workflow counts and recently updated workflows',\n icon: 'GitBranch',\n defaultSize: 'medium',\n removable: true,\n dataSource: 'workspace',\n availableInProducts: ['fluidgrids'],\n },\n workflowRuns: {\n id: 'workflowRuns',\n title: 'Workflow Runs',\n description: 'Run health, success rate, and recent executions',\n icon: 'Activity',\n defaultSize: 'medium',\n removable: true,\n dataSource: 'workspace',\n availableInProducts: ['fluidgrids'],\n },\n};\n\n// ─── Default Widget Layouts ───────────────────────────────────────────────────\n\nconst WORKSPACES_DEFAULT_WIDGETS: WidgetConfig[] = [\n { widgetId: 'welcome', enabled: true, order: 0, size: 'large' },\n { widgetId: 'quickActions', enabled: true, order: 1, size: 'medium' },\n { widgetId: 'workspaces', enabled: true, order: 2, size: 'medium' },\n { widgetId: 'projects', enabled: true, order: 3, size: 'medium' },\n { widgetId: 'activity', enabled: true, order: 4, size: 'large' },\n { widgetId: 'calendar', enabled: true, order: 5, size: 'medium' },\n { widgetId: 'workflows', enabled: true, order: 6, size: 'medium' },\n { widgetId: 'tags', enabled: true, order: 7, size: 'small' },\n { widgetId: 'storage', enabled: true, order: 8, size: 'small' },\n { widgetId: 'groups', enabled: true, order: 9, size: 'small' },\n { widgetId: 'members', enabled: true, order: 10, size: 'small' },\n { widgetId: 'tips', enabled: true, order: 11, size: 'small' },\n];\n\nconst VIBECONTROLS_DEFAULT_WIDGETS: WidgetConfig[] = [\n { widgetId: 'welcome', enabled: true, order: 0, size: 'large' },\n { widgetId: 'quickActions', enabled: true, order: 1, size: 'medium' },\n { widgetId: 'vibes', enabled: true, order: 2, size: 'medium' },\n { widgetId: 'sessions', enabled: true, order: 3, size: 'medium' },\n { widgetId: 'agents', enabled: true, order: 4, size: 'small' },\n { widgetId: 'workspaces', enabled: true, order: 5, size: 'medium' },\n { widgetId: 'projects', enabled: true, order: 6, size: 'medium' },\n { widgetId: 'activity', enabled: true, order: 7, size: 'large' },\n { widgetId: 'storage', enabled: true, order: 8, size: 'small' },\n { widgetId: 'members', enabled: true, order: 9, size: 'small' },\n { widgetId: 'tips', enabled: true, order: 10, size: 'small' },\n];\n\nconst FLUIDGRIDS_DEFAULT_WIDGETS: WidgetConfig[] = [\n { widgetId: 'welcome', enabled: true, order: 0, size: 'large' },\n { widgetId: 'quickActions', enabled: true, order: 1, size: 'medium' },\n { widgetId: 'workflowsOverview', enabled: true, order: 2, size: 'medium' },\n { widgetId: 'workflowRuns', enabled: true, order: 3, size: 'medium' },\n { widgetId: 'workspaces', enabled: true, order: 4, size: 'medium' },\n { widgetId: 'projects', enabled: true, order: 5, size: 'medium' },\n // Small widget placed right after projects so it fills the 1-column gap\n // left in that row (projects is 'medium' = 2 cols on a 3-col grid).\n { widgetId: 'tips', enabled: true, order: 6, size: 'small' },\n { widgetId: 'activity', enabled: true, order: 7, size: 'large' },\n { widgetId: 'groups', enabled: true, order: 8, size: 'small' },\n { widgetId: 'members', enabled: true, order: 9, size: 'small' },\n // tags/storage intentionally omitted from the default layout for FluidGrids\n // (not relevant to the workflow-automation use case) — still toggleable\n // via Customize, since getEffectiveWidgets() auto-appends them disabled.\n];\n\nexport const DEFAULT_DASHBOARD_WIDGETS_BY_PRODUCT: Record<string, WidgetConfig[]> = {\n workspaces: WORKSPACES_DEFAULT_WIDGETS,\n vibecontrols: VIBECONTROLS_DEFAULT_WIDGETS,\n fluidgrids: FLUIDGRIDS_DEFAULT_WIDGETS,\n};\n\n/** Backwards-compatible export — kept for callers that don't pass productId. */\nexport const DEFAULT_DASHBOARD_WIDGETS: WidgetConfig[] = WORKSPACES_DEFAULT_WIDGETS;\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nexport function isWidgetAvailable(\n widget: WidgetDefinition,\n productId: string = DEFAULT_PRODUCT_ID\n): boolean {\n if (!widget.availableInProducts || widget.availableInProducts.length === 0) {\n return true;\n }\n return widget.availableInProducts.includes(productId);\n}\n\nexport function getDefinitionsForProduct(\n productId: string = DEFAULT_PRODUCT_ID\n): Record<string, WidgetDefinition> {\n const out: Record<string, WidgetDefinition> = {};\n for (const [key, def] of Object.entries(WIDGET_DEFINITIONS)) {\n if (isWidgetAvailable(def, productId)) {\n out[key] = def;\n }\n }\n return out;\n}\n\n/**\n * Get the effective widget configuration, merging user prefs with defaults.\n * Filters out widgets that are not available for the given product.\n */\nexport function getEffectiveWidgets(\n userPrefs: WidgetConfig[] | null | undefined,\n productId: string = DEFAULT_PRODUCT_ID\n): WidgetConfig[] {\n const productDefaults =\n DEFAULT_DASHBOARD_WIDGETS_BY_PRODUCT[productId] ?? WORKSPACES_DEFAULT_WIDGETS;\n const availableDefinitions = getDefinitionsForProduct(productId);\n const availableIds = new Set(Object.keys(availableDefinitions));\n\n if (!userPrefs || userPrefs.length === 0) {\n return productDefaults.filter((w) => availableIds.has(w.widgetId));\n }\n\n // Drop saved prefs for widgets that no longer exist for this product (legacy\n // prefs e.g. systemHealth or workflows on vibecontrols).\n const filteredPrefs = userPrefs.filter((w) => availableIds.has(w.widgetId));\n const userWidgetIds = new Set(filteredPrefs.map((w) => w.widgetId));\n const merged = [...filteredPrefs];\n\n for (const def of Object.values(availableDefinitions)) {\n if (!userWidgetIds.has(def.id)) {\n merged.push({\n widgetId: def.id,\n enabled: false,\n order: merged.length,\n size: def.defaultSize,\n });\n }\n }\n\n return merged.sort((a, b) => a.order - b.order);\n}\n\n/**\n * Translate a widget definition's title/description with **literal** keys.\n *\n * `WIDGET_DEFINITIONS` is module scope, so it cannot call a hook, and the key\n * extractor (fluidgrids-app/scripts/extract-i18n-keys.ts) only collects string\n * literals — so neither a computed key nor a lookup table would ever be\n * extracted or seeded. Every widget is therefore spelled out. The English text\n * here stays in sync with `WIDGET_DEFINITIONS` above by being passed in as the\n * fallback, so it is impossible for the two to silently diverge. [BOFF-5997]\n */\nexport function resolveWidgetCopy(\n tr: (key: string, fallback: string) => string,\n def: WidgetDefinition\n): { title: string; description: string } {\n switch (def.id) {\n case 'welcome':\n return {\n title: tr('dashboard.widget.welcome.title', 'Welcome'),\n description: tr('dashboard.widget.welcome.description', 'Greeting and overview statistics'),\n };\n case 'quickActions':\n return {\n title: tr('dashboard.widget.quickActions.title', 'Quick Actions'),\n description: tr(\n 'dashboard.widget.quickActions.description',\n 'Frequently used actions and shortcuts'\n ),\n };\n case 'workspaces':\n return {\n title: tr('dashboard.widget.workspaces.title', 'Workspaces'),\n description: tr(\n 'dashboard.widget.workspaces.description',\n 'Your workspaces overview and quick access'\n ),\n };\n case 'projects':\n return {\n title: tr('dashboard.widget.projects.title', 'Projects'),\n description: tr(\n 'dashboard.widget.projects.description',\n 'Recent projects and status breakdown'\n ),\n };\n case 'activity':\n return {\n title: tr('dashboard.widget.activity.title', 'Recent Activity'),\n description: tr(\n 'dashboard.widget.activity.description',\n 'Timeline of recent activity across modules'\n ),\n };\n case 'calendar':\n return {\n title: tr('dashboard.widget.calendar.title', 'Upcoming Events'),\n description: tr('dashboard.widget.calendar.description', 'Next upcoming calendar events'),\n };\n case 'workflows':\n return {\n title: tr('dashboard.widget.workflows.title', 'Workflows'),\n description: tr(\n 'dashboard.widget.workflows.description',\n 'Active workflow stats and recent runs'\n ),\n };\n case 'tags':\n return {\n title: tr('dashboard.widget.tags.title', 'Tags'),\n description: tr('dashboard.widget.tags.description', 'Popular tags across your workspace'),\n };\n case 'storage':\n return {\n title: tr('dashboard.widget.storage.title', 'Storage'),\n description: tr(\n 'dashboard.widget.storage.description',\n 'Storage usage and file statistics'\n ),\n };\n case 'groups':\n return {\n title: tr('dashboard.widget.groups.title', 'Groups'),\n description: tr('dashboard.widget.groups.description', 'Your groups and teams'),\n };\n case 'members':\n return {\n title: tr('dashboard.widget.members.title', 'Members'),\n description: tr('dashboard.widget.members.description', 'Workspace member overview'),\n };\n case 'tips':\n return {\n title: tr('dashboard.widget.tips.title', 'Tips & Resources'),\n description: tr(\n 'dashboard.widget.tips.description',\n 'Getting started tips and documentation links'\n ),\n };\n case 'vibes':\n return {\n title: tr('dashboard.widget.vibes.title', 'Recent Vibes'),\n description: tr(\n 'dashboard.widget.vibes.description',\n 'Your most recently active development vibes'\n ),\n };\n case 'sessions':\n return {\n title: tr('dashboard.widget.sessions.title', 'Active Sessions'),\n description: tr(\n 'dashboard.widget.sessions.description',\n 'Running terminal and agent sessions'\n ),\n };\n case 'agents':\n return {\n title: tr('dashboard.widget.agents.title', 'Agent Fleet'),\n description: tr(\n 'dashboard.widget.agents.description',\n 'Online, offline, and degraded agents at a glance'\n ),\n };\n case 'workflowsOverview':\n return {\n title: tr('dashboard.widget.workflowsOverview.title', 'Workflows'),\n description: tr(\n 'dashboard.widget.workflowsOverview.description',\n 'Active/archived workflow counts and recently updated workflows'\n ),\n };\n case 'workflowRuns':\n return {\n title: tr('dashboard.widget.workflowRuns.title', 'Workflow Runs'),\n description: tr(\n 'dashboard.widget.workflowRuns.description',\n 'Run health, success rate, and recent executions'\n ),\n };\n default:\n return { title: def.title, description: def.description };\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useDashboardData.js","names":[],"sources":["../../../../src/pages/dashboard/hooks/useDashboardData.ts"],"sourcesContent":["/**\n * Dashboard Data Hooks\n *\n * Three parallel queries: workspace gateway (features + projects), global gateway.\n * Uses graphqlFetch from fe-libs with React Query for caching.\n *\n * IMPORTANT: Workspace/org/member data lives in the global tenant service\n * (global gateway), NOT in the workspace service (workspace gateway).\n * The workspace gateway is used for workspace-scoped features like\n * activity, calendar, tags, groups, projects, and workflows.\n *\n * All GraphQL operations are defined in src/operations/ and generated via codegen.\n * Document nodes are imported and converted to strings via print() for graphqlFetch.\n */\nimport { useQuery } from '@tanstack/react-query';\nimport { print } from 'graphql';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\nimport { useActiveContext } from '@burdenoff/fe-libs/shared/providers/shell';\nimport {\n DashboardProjectsDocument,\n DashboardWorkspaceDataDocument,\n} from '../../../generated/wspace-operations';\nimport { DashboardGlobalDataDocument } from '../../../generated/global-operations';\nimport type {\n WorkspaceDashboardData,\n GlobalDashboardData,\n VibeSummary,\n SessionSummary,\n AgentFleetSummary,\n} from '../types';\n\nconst BYTES_PER_GB = 1024 ** 3;\n\n// ─── Hooks ────────────────────────────────────────────────────────────────────\n\nexport function useProjectsDashboardData(authToken?: string | null) {\n const { workspaceId } = useActiveContext();\n\n return useQuery({\n queryKey: ['dashboard', 'projects', workspaceId],\n enabled: !!authToken && !!workspaceId,\n queryFn: async (): Promise<WorkspaceDashboardData['projects']> => {\n const result = await graphqlFetch<{\n projects: WorkspaceDashboardData['projects'];\n }>({\n gateway: 'workspace',\n authToken: authToken || undefined,\n workspaceToken: true,\n query: print(DashboardProjectsDocument),\n variables: { filter: { workspaceId } },\n // Dashboard widgets degrade gracefully (empty state) when a subgraph\n // is unavailable; a transient backend failure here is not actionable\n // for the user, so suppress the global error toast/console log.\n suppressGlobalErrorEvent: true,\n });\n\n return result.data?.projects ?? { items: [], total: 0 };\n },\n staleTime: 2 * 60 * 1000,\n gcTime: 5 * 60 * 1000,\n retry: 1,\n refetchOnWindowFocus: true,\n });\n}\n\nexport function useWorkspaceDashboardData(authToken?: string | null) {\n const { workspaceId } = useActiveContext();\n\n return useQuery({\n queryKey: ['dashboard', 'workspace', workspaceId],\n enabled: !!authToken && !!workspaceId,\n queryFn: async (): Promise<WorkspaceDashboardData> => {\n // Run the dashboard's recent-activity fetch as its OWN request so a\n // single failed sibling subgraph (workspace resource usage, workflow\n // dashboardSummary, …) doesn't null the whole batched response and\n // turn the widget's `recentActivities` into []. The combined\n // DashboardWorkspaceData query still feeds every other widget.\n const [result, activitiesResult] = await Promise.all([\n graphqlFetch<{\n getRecentActivities: WorkspaceDashboardData['recentActivities'];\n upcomingEvents: WorkspaceDashboardData['upcomingEvents'];\n getTags: WorkspaceDashboardData['tags'];\n myGroups: {\n edges: Array<{ node: { id: string; name: string; description?: string } }>;\n totalCount: number;\n };\n dashboardSummary: WorkspaceDashboardData['workflowStats'];\n workspaceResourceUsage: { currentStorageGb: number } | null;\n workspaceResourceLimits: { maxStorageGb?: number | null } | null;\n }>({\n gateway: 'workspace',\n authToken: authToken || undefined,\n workspaceToken: true,\n query: print(DashboardWorkspaceDataDocument),\n // This combined query fans out across several workspace subgraphs\n // (activity, calendar, tags, groups, workflow, files). A single\n // subgraph 500 nulls the whole response, but every field below is\n // null-coalesced to an empty state, so the dashboard still renders.\n // Suppress the global error toast/console log — a degraded subgraph\n // is not user-actionable here.\n suppressGlobalErrorEvent: true,\n }),\n graphqlFetch<{ getRecentActivities: WorkspaceDashboardData['recentActivities'] }>({\n gateway: 'workspace',\n authToken: authToken || undefined,\n workspaceToken: true,\n query:\n 'query DashboardRecentActivities { getRecentActivities(limit: 8) { id action resourceType resourceId userId description details timestamp } }',\n suppressGlobalErrorEvent: true,\n }),\n ]);\n\n // Gracefully handle partial failures — the gateway may return data for some\n // subgraphs and errors for others due to individual services being down.\n const data = result.data;\n const storageUsage = data?.workspaceResourceUsage\n ? {\n usedBytes: Math.round(data.workspaceResourceUsage.currentStorageGb * BYTES_PER_GB),\n totalBytes: Math.round(\n (data.workspaceResourceLimits?.maxStorageGb ?? 0) * BYTES_PER_GB\n ),\n fileCount: null,\n }\n : null;\n\n return {\n // Workspace data comes from the global gateway (see useGlobalDashboardData)\n // and is merged in at the DashboardPage level.\n workspaces: { items: [], total: 0 },\n // Projects fetched separately via useProjectsDashboardData to avoid\n // being poisoned by other subgraph errors in the combined query.\n projects: { items: [], total: 0 },\n recentActivities: activitiesResult.data?.getRecentActivities ?? [],\n upcomingEvents: data?.upcomingEvents ?? [],\n tags: data?.getTags ?? { items: [], total: 0 },\n groups: {\n items: (data?.myGroups?.edges ?? []).map((e) => ({\n id: e.node.id,\n name: e.node.name,\n description: e.node.description,\n })),\n totalCount: data?.myGroups?.totalCount ?? 0,\n },\n workflowStats: data?.dashboardSummary ?? null,\n storageUsage,\n };\n },\n staleTime: 2 * 60 * 1000, // 2 minutes — dashboard data changes moderately\n gcTime: 5 * 60 * 1000,\n retry: 1,\n refetchOnWindowFocus: true,\n });\n}\n\n// ─── VibeControls-specific dashboard data ────────────────────────────────────\n// Issued as raw GraphQL strings to avoid coupling to codegen for the new\n// `recentVibes` + `agentFleetSummary` queries (added in wspace-vibecontrols-svc).\n// Falls back to empty state if any field is missing in the deployed schema,\n// so the dashboard degrades gracefully during schema rollout.\n\nconst VIBECONTROLS_DASHBOARD_QUERY = `\n query VibeControlsDashboardData {\n recentVibes(limit: 5) {\n id\n name\n status\n workspaceId\n updatedAt\n }\n activeSessions(pagination: { limit: 5 }) {\n items {\n id\n name\n status\n agent { id name }\n startedAt\n vibeId\n }\n }\n agentFleetSummary {\n total\n online\n offline\n degraded\n lastSeenAt\n }\n }\n`;\n\ninterface VibecontrolsDashboardResponse {\n recentVibes?: VibeSummary[] | null;\n activeSessions?: { items?: SessionSummary[] | null } | null;\n agentFleetSummary?: AgentFleetSummary | null;\n}\n\nexport interface VibecontrolsDashboardData {\n recentVibes: VibeSummary[];\n activeSessions: SessionSummary[];\n agentFleet: AgentFleetSummary | null;\n}\n\nexport function useVibecontrolsDashboardData(authToken?: string | null, enabled = false) {\n const { workspaceId } = useActiveContext();\n\n return useQuery({\n queryKey: ['dashboard', 'vibecontrols', workspaceId],\n enabled: !!authToken && !!workspaceId && enabled,\n queryFn: async (): Promise<VibecontrolsDashboardData> => {\n try {\n const result = await graphqlFetch<VibecontrolsDashboardResponse>({\n gateway: 'workspace',\n authToken: authToken || undefined,\n workspaceToken: true,\n query: VIBECONTROLS_DASHBOARD_QUERY,\n // Schema may not be deployed in this environment; handled locally\n // (empty fallback below). Suppress the global error toast/log.\n suppressGlobalErrorEvent: true,\n });\n const data = result.data;\n return {\n recentVibes: data?.recentVibes ?? [],\n activeSessions: data?.activeSessions?.items ?? [],\n agentFleet: data?.agentFleetSummary ?? null,\n };\n } catch {\n // Schema may not yet be deployed in this environment — render empty.\n return { recentVibes: [], activeSessions: [], agentFleet: null };\n }\n },\n staleTime: 60 * 1000,\n gcTime: 5 * 60 * 1000,\n retry: 0,\n refetchOnWindowFocus: true,\n });\n}\n\nexport function useGlobalDashboardData(authToken?: string | null) {\n return useQuery({\n queryKey: ['dashboard', 'global'],\n enabled: !!authToken,\n queryFn: async (): Promise<GlobalDashboardData> => {\n const result = await graphqlFetch<{\n currentUser: GlobalDashboardData['currentUser'];\n myDashboardPreferences: Record<string, unknown> | null;\n myWorkspaces: {\n items: Array<{\n id: string;\n name: string;\n status: string;\n type: string;\n createdAt: string;\n memberCount: number;\n }>;\n total: number;\n };\n myOrganizations: {\n items: Array<{\n id: string;\n name: string;\n status: string;\n type: string;\n createdAt: string;\n }>;\n total: number;\n };\n }>({\n gateway: 'global',\n authToken: authToken || undefined,\n query: print(DashboardGlobalDataDocument),\n // Dashboard data is null-coalesced to empty state below; a transient\n // subgraph failure should not raise a user-facing error here.\n suppressGlobalErrorEvent: true,\n });\n\n const data = result.data;\n\n return {\n currentUser: data?.currentUser ?? null,\n dashboardPreferences: data?.myDashboardPreferences\n ? (data.myDashboardPreferences as unknown as GlobalDashboardData['dashboardPreferences'])\n : null,\n workspaces: data?.myWorkspaces ?? { items: [], total: 0 },\n organizations: data?.myOrganizations ?? { items: [], total: 0 },\n };\n },\n staleTime: 5 * 60 * 1000, // 5 minutes — user info and prefs change rarely\n gcTime: 10 * 60 * 1000,\n retry: 1,\n });\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useDashboardPreferences.js","names":[],"sources":["../../../../src/pages/dashboard/hooks/useDashboardPreferences.ts"],"sourcesContent":["/**\n * Dashboard Preferences Hook\n *\n * Manages dashboard widget layout preferences via the global gateway.\n * GraphQL operation defined in src/operations/dashboard/mutations/dashboard.graphql\n */\nimport { useMutation, useQueryClient } from '@tanstack/react-query';\nimport { print } from 'graphql';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\nimport { UpdateDashboardPreferencesDocument } from '../../../generated/global-operations';\nimport type { WidgetConfig, DashboardPreferences } from '../types';\n\nexport function useUpdateDashboardPreferences(authToken?: string | null) {\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationFn: async (widgets: WidgetConfig[]): Promise<DashboardPreferences> => {\n const result = await graphqlFetch<{\n updateMyDashboardPreferences: DashboardPreferences;\n }>({\n gateway: 'global',\n authToken: authToken || undefined,\n query: print(UpdateDashboardPreferencesDocument),\n variables: {\n input: {\n widgets: widgets.map((w) => ({\n widgetId: w.widgetId,\n enabled: w.enabled,\n order: w.order,\n size: w.size,\n })),\n },\n },\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message || 'Failed to save preferences');\n }\n\n return result.data!.updateMyDashboardPreferences;\n },\n onSuccess: () => {\n // Invalidate global dashboard data so the preferences are refreshed\n queryClient.invalidateQueries({ queryKey: ['dashboard', 'global'] });\n },\n });\n}\n"],"mappings":";;;;;AAYA,SAAgB,EAA8B,GAA2B;CACvE,IAAM,IAAc,GAAgB;AAEpC,QAAO,EAAY;EACjB,YAAY,OAAO,MAA2D;GAC5E,IAAM,IAAS,MAAM,EAElB;IACD,SAAS;IACT,WAAW,KAAa,KAAA;IACxB,OAAO,EAAM,EAAmC;IAChD,WAAW,EACT,OAAO,EACL,SAAS,EAAQ,KAAK,OAAO;KAC3B,UAAU,EAAE;KACZ,SAAS,EAAE;KACX,OAAO,EAAE;KACT,MAAM,EAAE;KACT,EAAE,EACJ,EACF;IACF,CAAC;AAEF,OAAI,EAAO,QAAQ,OACjB,OAAU,MAAM,EAAO,OAAO,IAAI,WAAW,6BAA6B;AAG5E,UAAO,EAAO,KAAM;;EAEtB,iBAAiB;AAEf,KAAY,kBAAkB,EAAE,UAAU,CAAC,aAAa,SAAS,EAAE,CAAC;;EAEvE,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"reorder.js","names":[],"sources":["../../../src/pages/dashboard/reorder.ts"],"sourcesContent":["/**\n * Pure reorder helpers for the dashboard customizer drag-and-drop.\n *\n * Extracted from `DashboardCustomizer.tsx` so the ordering logic can be unit-\n * tested without DOM/React Test Renderer. The caller owns the live `WidgetConfig[]`\n * state and is responsible for cloning before mutating.\n *\n * Invariants:\n * - Input widgets are expected to already be sorted by `order` ascending. The\n * helper re-sorts defensively so callers don't have to.\n * - `enabled` and `size` are preserved on every widget; only `order` changes.\n * - Drop positions are evaluated relative to the row the pointer is over: the\n * upper half means \"insert before\", the lower half means \"insert after\".\n */\nimport type { WidgetConfig } from './types';\n\n/** Side of the target row the pointer is on, relative to the row midpoint. */\nexport type DropPosition = 'above' | 'below';\n\nconst ORDER_STEP = 10;\n\n/**\n * Re-number `order` so it is strictly increasing by `ORDER_STEP` from the\n * given array's order. Returns a fresh array; input is not mutated.\n */\nexport function renumberOrders(widgets: WidgetConfig[]): WidgetConfig[] {\n return widgets.map((w, idx) => ({ ...w, order: (idx + 1) * ORDER_STEP }));\n}\n\n/**\n * Move `fromId` to a position adjacent to `toId`.\n *\n * - `position === 'above'` — `fromId` ends up immediately before `toId`.\n * - `position === 'below'` — `fromId` ends up immediately after `toId`.\n *\n * If either id is missing, or `fromId === toId`, the input is returned\n * unchanged (referential equality preserved). Adjacent swaps are valid\n * moves — flicker prevention is the caller's responsibility (see\n * `DashboardCustomizer.handleDragOver`, which tracks the last-applied\n * move via a ref). [BOFF-7219]\n */\nexport function reorderByDropIndex(\n widgets: WidgetConfig[],\n fromId: string,\n toId: string,\n position: DropPosition\n): WidgetConfig[] {\n if (fromId === toId) return widgets;\n\n const sorted = [...widgets].sort((a, b) => a.order - b.order);\n const fromIdx = sorted.findIndex((w) => w.widgetId === fromId);\n const toIdx = sorted.findIndex((w) => w.widgetId === toId);\n if (fromIdx < 0 || toIdx < 0) return widgets;\n\n const moving = sorted[fromIdx];\n const without = sorted.filter((_, i) => i !== fromIdx);\n const insertIdx =\n position === 'above'\n ? without.findIndex((w) => w.widgetId === toId)\n : without.findIndex((w) => w.widgetId === toId) + 1;\n\n const reordered = [...without.slice(0, insertIdx), moving, ...without.slice(insertIdx)];\n return renumberOrders(reordered);\n}\n\n/**\n * Resolve the drop position from a pointer event and a target row's bounding\n * rect. `clientY` above the midpoint yields 'above', otherwise 'below'.\n */\nexport function dropPositionFromPointer(clientY: number, rect: DOMRect): DropPosition {\n return clientY < rect.top + rect.height / 2 ? 'above' : 'below';\n}\n"],"mappings":";AAmBA,IAAM,IAAa;AAMnB,SAAgB,EAAe,GAAyC;AACtE,QAAO,EAAQ,KAAK,GAAG,OAAS;EAAE,GAAG;EAAG,QAAQ,IAAM,KAAK;EAAY,EAAE;;AAe3E,SAAgB,EACd,GACA,GACA,GACA,GACgB;AAChB,KAAI,MAAW,EAAM,QAAO;CAE5B,IAAM,IAAS,CAAC,GAAG,EAAQ,CAAC,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM,EACvD,IAAU,EAAO,WAAW,MAAM,EAAE,aAAa,EAAO,EACxD,IAAQ,EAAO,WAAW,MAAM,EAAE,aAAa,EAAK;AAC1D,KAAI,IAAU,KAAK,IAAQ,EAAG,QAAO;CAErC,IAAM,IAAS,EAAO,IAChB,IAAU,EAAO,QAAQ,GAAG,MAAM,MAAM,EAAQ,EAChD,IACJ,MAAa,UACT,EAAQ,WAAW,MAAM,EAAE,aAAa,EAAK,GAC7C,EAAQ,WAAW,MAAM,EAAE,aAAa,EAAK,GAAG;AAGtD,QAAO,EADW;EAAC,GAAG,EAAQ,MAAM,GAAG,EAAU;EAAE;EAAQ,GAAG,EAAQ,MAAM,EAAU;EAAC,CACvD;;AAOlC,SAAgB,EAAwB,GAAiB,GAA6B;AACpF,QAAO,IAAU,EAAK,MAAM,EAAK,SAAS,IAAI,UAAU"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"AgentsWidget.js","names":[],"sources":["../../../../src/pages/dashboard/widgets/AgentsWidget.tsx"],"sourcesContent":["import { Bot, ArrowRight, Wifi, WifiOff, AlertTriangle } from 'lucide-react';\nimport { Button, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { WidgetWrapper } from '../WidgetWrapper';\nimport { tWithFallback } from '../../../utils/i18n';\nimport type { WidgetProps } from '../types';\n\nexport function AgentsWidget({ workspaceData, isLoading, navigate }: WidgetProps) {\n const { t } = useI18n();\n const fleet = workspaceData?.agentFleet ?? null;\n const total = fleet?.total ?? 0;\n\n return (\n <WidgetWrapper\n title={tWithFallback(t, 'dashboard.agents.title', 'Agent Fleet')}\n icon={<Bot className=\"size-4\" />}\n size=\"small\"\n isLoading={isLoading}\n unavailable={!workspaceData}\n headerAction={\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"text-xs\"\n onClick={() => navigate('/vibecontrols/agents')}\n >\n {tWithFallback(t, 'dashboard.agents.open', 'Open')} <ArrowRight className=\"ml-1 size-3\" />\n </Button>\n }\n >\n {!fleet || total === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-agents\"\n title={tWithFallback(t, 'dashboard.agents.emptyTitle', 'No agents connected')}\n className=\"py-4\"\n action={\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => navigate('/vibecontrols/agents/new')}\n >\n {tWithFallback(t, 'dashboard.agents.connectAgent', 'Connect agent')}\n </Button>\n }\n />\n ) : (\n <div className=\"space-y-3\">\n <p className=\"text-2xl font-semibold text-text-primary\">\n {total}\n <span className=\"ml-1 text-xs font-normal text-text-secondary\">\n {tWithFallback(t, 'dashboard.agents.total', 'total')}\n </span>\n </p>\n <div className=\"grid grid-cols-3 gap-2 text-center\">\n <div className=\"rounded-md bg-status-success-bg/30 p-2\">\n <Wifi\n className={`mx-auto size-4 text-status-success-text ${\n fleet.online > 0 ? 'motion-safe:animate-vc-glow-pulse' : ''\n }`}\n />\n <p className=\"mt-1 text-sm font-semibold text-text-primary\">{fleet.online}</p>\n <p className=\"text-[10px] text-text-secondary\">\n {tWithFallback(t, 'dashboard.agents.online', 'online')}\n </p>\n </div>\n <div className=\"rounded-md bg-status-warning-bg/30 p-2\">\n <AlertTriangle className=\"mx-auto size-4 text-status-warning-text\" />\n <p className=\"mt-1 text-sm font-semibold text-text-primary\">{fleet.degraded}</p>\n <p className=\"text-[10px] text-text-secondary\">\n {tWithFallback(t, 'dashboard.agents.degraded', 'degraded')}\n </p>\n </div>\n <div className=\"rounded-md bg-text-tertiary/10 p-2\">\n <WifiOff className=\"mx-auto size-4 text-text-tertiary\" />\n <p className=\"mt-1 text-sm font-semibold text-text-primary\">{fleet.offline}</p>\n <p className=\"text-[10px] text-text-secondary\">\n {tWithFallback(t, 'dashboard.agents.offline', 'offline')}\n </p>\n </div>\n </div>\n {fleet.lastSeenAt ? (\n <p className=\"text-[11px] text-text-secondary text-center\">\n {tWithFallback(t, 'dashboard.agents.lastSeen', 'Last seen {time}', {\n time: new Date(fleet.lastSeenAt).toLocaleString(undefined, {\n hour: '2-digit',\n minute: '2-digit',\n month: 'short',\n day: 'numeric',\n }),\n })}\n </p>\n ) : null}\n </div>\n )}\n </WidgetWrapper>\n );\n}\n"],"mappings":";;;;;;;AAOA,SAAgB,EAAa,EAAE,kBAAe,cAAW,eAAyB;CAChF,IAAM,EAAE,SAAM,GAAS,EACjB,IAAQ,GAAe,cAAc,MACrC,IAAQ,GAAO,SAAS;AAE9B,QACE,kBAAC,GAAD;EACE,OAAO,EAAc,GAAG,0BAA0B,cAAc;EAChE,MAAM,kBAAC,GAAD,EAAK,WAAU,UAAW,CAAA;EAChC,MAAK;EACM;EACX,aAAa,CAAC;EACd,cACE,kBAAC,GAAD;GACE,SAAQ;GACR,MAAK;GACL,WAAU;GACV,eAAe,EAAS,uBAAuB;aAJjD;IAMG,EAAc,GAAG,yBAAyB,OAAO;IAAC;IAAC,kBAAC,GAAD,EAAY,WAAU,eAAgB,CAAA;IACnF;;YAGV,CAAC,KAAS,MAAU,IACnB,kBAAC,GAAD;GACE,cAAa;GACb,OAAO,EAAc,GAAG,+BAA+B,sBAAsB;GAC7E,WAAU;GACV,QACE,kBAAC,GAAD;IACE,SAAQ;IACR,MAAK;IACL,eAAe,EAAS,2BAA2B;cAElD,EAAc,GAAG,iCAAiC,gBAAgB;IAC5D,CAAA;GAEX,CAAA,GAEF,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,KAAD;KAAG,WAAU;eAAb,CACG,GACD,kBAAC,QAAD;MAAM,WAAU;gBACb,EAAc,GAAG,0BAA0B,QAAQ;MAC/C,CAAA,CACL;;IACJ,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,GAAD,EACE,WAAW,2CACT,EAAM,SAAS,IAAI,sCAAsC,MAE3D,CAAA;QACF,kBAAC,KAAD;SAAG,WAAU;mBAAgD,EAAM;SAAW,CAAA;QAC9E,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAc,GAAG,2BAA2B,SAAS;SACpD,CAAA;QACA;;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,GAAD,EAAe,WAAU,2CAA4C,CAAA;QACrE,kBAAC,KAAD;SAAG,WAAU;mBAAgD,EAAM;SAAa,CAAA;QAChF,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAc,GAAG,6BAA6B,WAAW;SACxD,CAAA;QACA;;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,GAAD,EAAS,WAAU,qCAAsC,CAAA;QACzD,kBAAC,KAAD;SAAG,WAAU;mBAAgD,EAAM;SAAY,CAAA;QAC/E,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAc,GAAG,4BAA4B,UAAU;SACtD,CAAA;QACA;;MACF;;IACL,EAAM,aACL,kBAAC,KAAD;KAAG,WAAU;eACV,EAAc,GAAG,6BAA6B,oBAAoB,EACjE,MAAM,IAAI,KAAK,EAAM,WAAW,CAAC,eAAe,KAAA,GAAW;MACzD,MAAM;MACN,QAAQ;MACR,OAAO;MACP,KAAK;MACN,CAAC,EACH,CAAC;KACA,CAAA,GACF;IACA;;EAEM,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"GroupsWidget.js","names":[],"sources":["../../../../src/pages/dashboard/widgets/GroupsWidget.tsx"],"sourcesContent":["import { Users, ArrowRight } from 'lucide-react';\nimport { Button, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { WidgetWrapper } from '../WidgetWrapper';\nimport { tWithFallback } from '../../../utils/i18n';\nimport type { WidgetProps } from '../types';\n\nexport function GroupsWidget({ workspaceData, isLoading, navigate }: WidgetProps) {\n const { t } = useI18n();\n const groups = workspaceData?.groups.items ?? [];\n const total = workspaceData?.groups.totalCount ?? 0;\n\n return (\n <WidgetWrapper\n title={tWithFallback(t, 'dashboard.groups.title', 'Groups')}\n icon={<Users className=\"size-4\" />}\n size=\"small\"\n isLoading={isLoading}\n unavailable={!workspaceData}\n headerAction={\n <Button variant=\"ghost\" size=\"sm\" className=\"text-xs\" onClick={() => navigate('/groups')}>\n <ArrowRight className=\"size-3\" />\n </Button>\n }\n >\n {groups.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title={tWithFallback(t, 'dashboard.groups.emptyTitle', 'No groups yet')}\n description={tWithFallback(\n t,\n 'dashboard.groups.emptyDescription',\n 'Groups bundle members together so you can manage and mention them as one.'\n )}\n className=\"py-4\"\n />\n ) : (\n <div className=\"space-y-1.5\">\n {groups.slice(0, 4).map((group) => (\n <button\n key={group.id}\n type=\"button\"\n className=\"w-full flex items-center gap-2 p-1.5 rounded hover:bg-accent/5 cursor-pointer transition-colors text-left\"\n onClick={() => navigate('/groups')}\n >\n <div className=\"size-7 rounded-full bg-accent-purple-subtle text-accent-purple flex items-center justify-center text-xs font-bold shrink-0\">\n {group.name.charAt(0).toUpperCase()}\n </div>\n <div className=\"min-w-0\">\n <p className=\"text-sm font-medium text-text-primary truncate\">{group.name}</p>\n {group.description && (\n <p className=\"text-[11px] text-text-secondary truncate\">{group.description}</p>\n )}\n </div>\n </button>\n ))}\n {total > 4 && (\n <p className=\"text-[11px] text-text-secondary text-center\">\n {tWithFallback(t, 'dashboard.groups.more', '+{count} more groups', {\n count: total - 4,\n })}\n </p>\n )}\n </div>\n )}\n </WidgetWrapper>\n );\n}\n"],"mappings":";;;;;;;AAOA,SAAgB,EAAa,EAAE,kBAAe,cAAW,eAAyB;CAChF,IAAM,EAAE,SAAM,GAAS,EACjB,IAAS,GAAe,OAAO,SAAS,EAAE,EAC1C,IAAQ,GAAe,OAAO,cAAc;AAElD,QACE,kBAAC,GAAD;EACE,OAAO,EAAc,GAAG,0BAA0B,SAAS;EAC3D,MAAM,kBAAC,GAAD,EAAO,WAAU,UAAW,CAAA;EAClC,MAAK;EACM;EACX,aAAa,CAAC;EACd,cACE,kBAAC,GAAD;GAAQ,SAAQ;GAAQ,MAAK;GAAK,WAAU;GAAU,eAAe,EAAS,UAAU;aACtF,kBAAC,GAAD,EAAY,WAAU,UAAW,CAAA;GAC1B,CAAA;YAGV,EAAO,WAAW,IACjB,kBAAC,GAAD;GACE,cAAa;GACb,OAAO,EAAc,GAAG,+BAA+B,gBAAgB;GACvE,aAAa,EACX,GACA,qCACA,4EACD;GACD,WAAU;GACV,CAAA,GAEF,kBAAC,OAAD;GAAK,WAAU;aAAf,CACG,EAAO,MAAM,GAAG,EAAE,CAAC,KAAK,MACvB,kBAAC,UAAD;IAEE,MAAK;IACL,WAAU;IACV,eAAe,EAAS,UAAU;cAJpC,CAME,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAM,KAAK,OAAO,EAAE,CAAC,aAAa;KAC/B,CAAA,EACN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,KAAD;MAAG,WAAU;gBAAkD,EAAM;MAAS,CAAA,EAC7E,EAAM,eACL,kBAAC,KAAD;MAAG,WAAU;gBAA4C,EAAM;MAAgB,CAAA,CAE7E;OACC;MAdF,EAAM,GAcJ,CACT,EACD,IAAQ,KACP,kBAAC,KAAD;IAAG,WAAU;cACV,EAAc,GAAG,yBAAyB,wBAAwB,EACjE,OAAO,IAAQ,GAChB,CAAC;IACA,CAAA,CAEF;;EAEM,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"MembersWidget.js","names":[],"sources":["../../../../src/pages/dashboard/widgets/MembersWidget.tsx"],"sourcesContent":["import { Users, UserPlus, ArrowRight } from 'lucide-react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { WidgetWrapper } from '../WidgetWrapper';\nimport { tWithFallback } from '../../../utils/i18n';\nimport type { WidgetProps } from '../types';\n\nexport function MembersWidget({ workspaceData, globalData, isLoading, navigate }: WidgetProps) {\n const { t } = useI18n();\n\n // Member count comes from globalData.workspaces (each workspace has memberCount)\n const workspaces = globalData?.workspaces?.items ?? workspaceData?.workspaces.items ?? [];\n const totalMembers = workspaces.reduce(\n (sum, ws) => sum + ((ws as { memberCount?: number }).memberCount ?? 0),\n 0\n );\n const totalWorkspaces = globalData?.workspaces?.total ?? workspaceData?.workspaces.total ?? 0;\n\n return (\n <WidgetWrapper\n title={tWithFallback(t, 'dashboard.members.title', 'Members')}\n icon={<Users className=\"size-4\" />}\n size=\"small\"\n isLoading={isLoading}\n unavailable={!workspaceData && !globalData}\n headerAction={\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"text-xs\"\n onClick={() => navigate('/workspaces/members')}\n >\n <ArrowRight className=\"size-3\" />\n </Button>\n }\n >\n <div className=\"space-y-3\">\n <div className=\"text-center\">\n <p className=\"text-3xl font-bold text-text-primary tabular-nums\">{totalMembers}</p>\n <p className=\"text-xs text-text-secondary\">\n {tWithFallback(\n t,\n 'dashboard.members.summary',\n '{memberLabel} across {count} {workspaceLabel}',\n {\n memberLabel:\n totalMembers === 1\n ? tWithFallback(t, 'dashboard.members.memberSingular', 'Member')\n : tWithFallback(t, 'dashboard.members.memberPlural', 'Members'),\n count: totalWorkspaces,\n workspaceLabel:\n totalWorkspaces === 1\n ? tWithFallback(t, 'dashboard.members.workspaceSingular', 'workspace')\n : tWithFallback(t, 'dashboard.members.workspacePlural', 'workspaces'),\n }\n )}\n </p>\n </div>\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"w-full text-xs\"\n onClick={() => navigate('/workspaces/members?invite=true')}\n >\n <UserPlus className=\"size-3 mr-1.5\" />\n {tWithFallback(t, 'dashboard.members.invite', 'Invite a member')}\n </Button>\n {workspaces.length > 0 && (\n <div className=\"flex -space-x-2 justify-center\">\n {workspaces.slice(0, 5).map((ws) => (\n <div\n key={ws.id}\n className=\"size-8 rounded-full bg-action-primary-bg/10 text-primary flex items-center justify-center text-xs font-bold border-2 border-bg-surface\"\n title={ws.name}\n >\n {ws.name.charAt(0).toUpperCase()}\n </div>\n ))}\n {totalWorkspaces > 5 && (\n <div className=\"size-8 rounded-full bg-bg-sunken text-text-secondary flex items-center justify-center text-xs font-bold border-2 border-bg-surface\">\n +{totalWorkspaces - 5}\n </div>\n )}\n </div>\n )}\n </div>\n </WidgetWrapper>\n );\n}\n"],"mappings":";;;;;;;AAOA,SAAgB,EAAc,EAAE,kBAAe,eAAY,cAAW,eAAyB;CAC7F,IAAM,EAAE,SAAM,GAAS,EAGjB,IAAa,GAAY,YAAY,SAAS,GAAe,WAAW,SAAS,EAAE,EACnF,IAAe,EAAW,QAC7B,GAAK,MAAO,KAAQ,EAAgC,eAAe,IACpE,EACD,EACK,IAAkB,GAAY,YAAY,SAAS,GAAe,WAAW,SAAS;AAE5F,QACE,kBAAC,GAAD;EACE,OAAO,EAAc,GAAG,2BAA2B,UAAU;EAC7D,MAAM,kBAAC,GAAD,EAAO,WAAU,UAAW,CAAA;EAClC,MAAK;EACM;EACX,aAAa,CAAC,KAAiB,CAAC;EAChC,cACE,kBAAC,GAAD;GACE,SAAQ;GACR,MAAK;GACL,WAAU;GACV,eAAe,EAAS,sBAAsB;aAE9C,kBAAC,GAAD,EAAY,WAAU,UAAW,CAAA;GAC1B,CAAA;YAGX,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,KAAD;MAAG,WAAU;gBAAqD;MAAiB,CAAA,EACnF,kBAAC,KAAD;MAAG,WAAU;gBACV,EACC,GACA,6BACA,iDACA;OACE,aACE,MAAiB,IACb,EAAc,GAAG,oCAAoC,SAAS,GAC9D,EAAc,GAAG,kCAAkC,UAAU;OACnE,OAAO;OACP,gBACE,MAAoB,IAChB,EAAc,GAAG,uCAAuC,YAAY,GACpE,EAAc,GAAG,qCAAqC,aAAa;OAC1E,CACF;MACC,CAAA,CACA;;IACN,kBAAC,GAAD;KACE,SAAQ;KACR,MAAK;KACL,WAAU;KACV,eAAe,EAAS,kCAAkC;eAJ5D,CAME,kBAAC,GAAD,EAAU,WAAU,iBAAkB,CAAA,EACrC,EAAc,GAAG,4BAA4B,kBAAkB,CACzD;;IACR,EAAW,SAAS,KACnB,kBAAC,OAAD;KAAK,WAAU;eAAf,CACG,EAAW,MAAM,GAAG,EAAE,CAAC,KAAK,MAC3B,kBAAC,OAAD;MAEE,WAAU;MACV,OAAO,EAAG;gBAET,EAAG,KAAK,OAAO,EAAE,CAAC,aAAa;MAC5B,EALC,EAAG,GAKJ,CACN,EACD,IAAkB,KACjB,kBAAC,OAAD;MAAK,WAAU;gBAAf,CAAoJ,KAChJ,IAAkB,EAChB;QAEJ;;IAEJ;;EACQ,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectsWidget.js","names":[],"sources":["../../../../src/pages/dashboard/widgets/ProjectsWidget.tsx"],"sourcesContent":["import { FolderKanban, ArrowRight, Circle } from 'lucide-react';\nimport { Badge, Button, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { WidgetWrapper } from '../WidgetWrapper';\nimport { tWithFallback } from '../../../utils/i18n';\nimport type { WidgetProps } from '../types';\n\nconst statusColors: Record<string, string> = {\n ACTIVE: 'bg-status-success-bg',\n INACTIVE: 'bg-text-tertiary',\n ARCHIVED: 'bg-status-warning-bg',\n};\n\nconst statusVariant: Record<string, 'default' | 'secondary' | 'outline'> = {\n ACTIVE: 'default',\n INACTIVE: 'secondary',\n ARCHIVED: 'outline',\n};\n\nexport function ProjectsWidget({ workspaceData, isLoading, navigate }: WidgetProps) {\n const { t } = useI18n();\n const projects = workspaceData?.projects.items ?? [];\n const total = workspaceData?.projects.total ?? 0;\n\n // Status breakdown\n const activeCount = projects.filter((p) => p.status === 'ACTIVE').length;\n const inactiveCount = projects.filter((p) => p.status === 'INACTIVE').length;\n\n return (\n <WidgetWrapper\n title={tWithFallback(t, 'dashboard.projects.title', 'Projects')}\n icon={<FolderKanban className=\"size-4\" />}\n size=\"medium\"\n isLoading={isLoading}\n unavailable={!workspaceData}\n headerAction={\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"text-xs\"\n onClick={() => navigate('/workspaces/projects')}\n >\n {tWithFallback(t, 'dashboard.projects.viewAll', 'View all')}{' '}\n <ArrowRight className=\"ml-1 size-3\" />\n </Button>\n }\n >\n {projects.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={tWithFallback(t, 'dashboard.projects.emptyTitle', 'No projects yet')}\n className=\"py-4\"\n action={\n <Button variant=\"outline\" size=\"sm\" onClick={() => navigate('/workspaces/projects')}>\n {tWithFallback(t, 'dashboard.projects.createProject', 'Create a project')}\n </Button>\n }\n />\n ) : (\n <div className=\"space-y-3\">\n {total > 0 && (\n <div className=\"flex items-center gap-3 text-xs text-text-secondary\">\n <span className=\"flex items-center gap-1\">\n <Circle\n className={`size-2 fill-status-success-bg text-status-success-bg ${\n activeCount > 0 ? 'motion-safe:animate-vc-glow-pulse' : ''\n }`}\n />\n {tWithFallback(t, 'dashboard.projects.activeCount', '{count} active', {\n count: activeCount,\n })}\n </span>\n {inactiveCount > 0 && (\n <span className=\"flex items-center gap-1\">\n <Circle className=\"size-2 fill-text-tertiary text-text-tertiary\" />\n {tWithFallback(t, 'dashboard.projects.inactiveCount', '{count} inactive', {\n count: inactiveCount,\n })}\n </span>\n )}\n <span className=\"ml-auto font-medium\">\n {tWithFallback(t, 'dashboard.projects.totalCount', '{count} total', {\n count: total,\n })}\n </span>\n </div>\n )}\n <div className=\"space-y-2\">\n {projects.slice(0, 4).map((proj) => (\n <button\n key={proj.id}\n type=\"button\"\n className=\"w-full flex items-center justify-between p-2 rounded-md hover:bg-accent/5 cursor-pointer transition-colors text-left\"\n onClick={() => navigate(`/workspaces/${proj.workspaceId}/projects`)}\n >\n <div className=\"flex items-center gap-2 min-w-0\">\n <Circle\n className={`size-2 ${statusColors[proj.status] || 'bg-text-tertiary'} fill-current ${\n proj.status === 'ACTIVE' ? 'motion-safe:animate-vc-glow-pulse' : ''\n }`}\n />\n <span className=\"text-sm font-medium text-text-primary truncate\">\n {proj.name}\n </span>\n </div>\n <Badge\n variant={statusVariant[proj.status] || 'outline'}\n className=\"text-[10px] shrink-0\"\n >\n {proj.key}\n </Badge>\n </button>\n ))}\n </div>\n </div>\n )}\n </WidgetWrapper>\n );\n}\n"],"mappings":";;;;;;;AAOA,IAAM,IAAuC;CAC3C,QAAQ;CACR,UAAU;CACV,UAAU;CACX,EAEK,IAAqE;CACzE,QAAQ;CACR,UAAU;CACV,UAAU;CACX;AAED,SAAgB,EAAe,EAAE,kBAAe,cAAW,eAAyB;CAClF,IAAM,EAAE,SAAM,GAAS,EACjB,IAAW,GAAe,SAAS,SAAS,EAAE,EAC9C,IAAQ,GAAe,SAAS,SAAS,GAGzC,IAAc,EAAS,QAAQ,MAAM,EAAE,WAAW,SAAS,CAAC,QAC5D,IAAgB,EAAS,QAAQ,MAAM,EAAE,WAAW,WAAW,CAAC;AAEtE,QACE,kBAAC,GAAD;EACE,OAAO,EAAc,GAAG,4BAA4B,WAAW;EAC/D,MAAM,kBAAC,GAAD,EAAc,WAAU,UAAW,CAAA;EACzC,MAAK;EACM;EACX,aAAa,CAAC;EACd,cACE,kBAAC,GAAD;GACE,SAAQ;GACR,MAAK;GACL,WAAU;GACV,eAAe,EAAS,uBAAuB;aAJjD;IAMG,EAAc,GAAG,8BAA8B,WAAW;IAAE;IAC7D,kBAAC,GAAD,EAAY,WAAU,eAAgB,CAAA;IAC/B;;YAGV,EAAS,WAAW,IACnB,kBAAC,GAAD;GACE,cAAa;GACb,OAAO,EAAc,GAAG,iCAAiC,kBAAkB;GAC3E,WAAU;GACV,QACE,kBAAC,GAAD;IAAQ,SAAQ;IAAU,MAAK;IAAK,eAAe,EAAS,uBAAuB;cAChF,EAAc,GAAG,oCAAoC,mBAAmB;IAClE,CAAA;GAEX,CAAA,GAEF,kBAAC,OAAD;GAAK,WAAU;aAAf,CACG,IAAQ,KACP,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,QAAD;MAAM,WAAU;gBAAhB,CACE,kBAAC,GAAD,EACE,WAAW,wDACT,IAAc,IAAI,sCAAsC,MAE1D,CAAA,EACD,EAAc,GAAG,kCAAkC,kBAAkB,EACpE,OAAO,GACR,CAAC,CACG;;KACN,IAAgB,KACf,kBAAC,QAAD;MAAM,WAAU;gBAAhB,CACE,kBAAC,GAAD,EAAQ,WAAU,gDAAiD,CAAA,EAClE,EAAc,GAAG,oCAAoC,oBAAoB,EACxE,OAAO,GACR,CAAC,CACG;;KAET,kBAAC,QAAD;MAAM,WAAU;gBACb,EAAc,GAAG,iCAAiC,iBAAiB,EAClE,OAAO,GACR,CAAC;MACG,CAAA;KACH;OAER,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAS,MAAM,GAAG,EAAE,CAAC,KAAK,MACzB,kBAAC,UAAD;KAEE,MAAK;KACL,WAAU;KACV,eAAe,EAAS,eAAe,EAAK,YAAY,WAAW;eAJrE,CAME,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD,EACE,WAAW,UAAU,EAAa,EAAK,WAAW,mBAAmB,gBACnE,EAAK,WAAW,WAAW,sCAAsC,MAEnE,CAAA,EACF,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAK;OACD,CAAA,CACH;SACN,kBAAC,GAAD;MACE,SAAS,EAAc,EAAK,WAAW;MACvC,WAAU;gBAET,EAAK;MACA,CAAA,CACD;OArBF,EAAK,GAqBH,CACT;IACE,CAAA,CACF;;EAEM,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"QuickActionsWidget.js","names":[],"sources":["../../../../src/pages/dashboard/widgets/QuickActionsWidget.tsx"],"sourcesContent":["import type { LucideIcon } from 'lucide-react';\nimport {\n Zap,\n Plus,\n Upload,\n UserPlus,\n FolderKanban,\n Calendar,\n FileText,\n Tags,\n Bot,\n Terminal,\n Sparkles,\n Cog,\n GitBranch,\n PlayCircle,\n LayoutTemplate,\n} from 'lucide-react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport type { I18nContextValue } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { WidgetWrapper } from '../WidgetWrapper';\nimport { tWithFallback } from '../../../utils/i18n';\nimport type { WidgetProps } from '../types';\n\ntype Translate = I18nContextValue['t'];\n\ninterface QuickAction {\n id: string;\n label: string;\n icon: LucideIcon;\n path: string;\n color: string;\n}\n\nfunction workspacesQuickActions(t: Translate): QuickAction[] {\n return [\n {\n id: 'create-workspace',\n label: tWithFallback(t, 'dashboard.quickActions.newWorkspace', 'New Workspace'),\n icon: Plus,\n path: '/workspaces/list',\n color: 'text-accent-blue',\n },\n {\n id: 'create-project',\n label: tWithFallback(t, 'dashboard.quickActions.newProject', 'New Project'),\n icon: FolderKanban,\n path: '/workspaces/projects',\n color: 'text-status-success-text',\n },\n {\n id: 'invite-member',\n label: tWithFallback(t, 'dashboard.quickActions.inviteMember', 'Invite Member'),\n icon: UserPlus,\n path: '/workspaces/members',\n color: 'text-accent-purple',\n },\n {\n id: 'upload-file',\n label: tWithFallback(t, 'dashboard.quickActions.uploadFile', 'Upload File'),\n icon: Upload,\n path: '/files',\n color: 'text-status-warning-text',\n },\n {\n id: 'new-event',\n label: tWithFallback(t, 'dashboard.quickActions.newEvent', 'New Event'),\n icon: Calendar,\n path: '/calendar',\n color: 'text-accent-pink',\n },\n {\n id: 'manage-tags',\n label: tWithFallback(t, 'dashboard.quickActions.manageTags', 'Manage Tags'),\n icon: Tags,\n path: '/tags',\n color: 'text-accent-cyan',\n },\n {\n id: 'view-workflows',\n label: tWithFallback(t, 'dashboard.quickActions.workflows', 'Workflows'),\n icon: FileText,\n path: '/workflows',\n color: 'text-accent-orange',\n },\n {\n id: 'settings',\n label: tWithFallback(t, 'dashboard.quickActions.settings', 'Settings'),\n icon: Cog,\n path: '/settings',\n color: 'text-text-secondary',\n },\n ];\n}\n\nfunction vibecontrolsQuickActions(t: Translate): QuickAction[] {\n return [\n {\n id: 'new-vibe',\n label: tWithFallback(t, 'dashboard.quickActions.newVibe', 'New Vibe'),\n icon: Sparkles,\n path: '/vibecontrols/vibes/new',\n color: 'text-accent-blue',\n },\n {\n id: 'view-sessions',\n label: tWithFallback(t, 'dashboard.quickActions.sessions', 'Sessions'),\n icon: Terminal,\n path: '/vibecontrols/sessions',\n color: 'text-status-success-text',\n },\n {\n id: 'connect-agent',\n label: tWithFallback(t, 'dashboard.quickActions.connectAgent', 'Connect Agent'),\n icon: Bot,\n path: '/vibecontrols/agents/new',\n color: 'text-accent-purple',\n },\n {\n id: 'invite-member',\n label: tWithFallback(t, 'dashboard.quickActions.inviteMember', 'Invite Member'),\n icon: UserPlus,\n path: '/workspaces/members',\n color: 'text-accent-pink',\n },\n {\n id: 'upload-file',\n label: tWithFallback(t, 'dashboard.quickActions.uploadFile', 'Upload File'),\n icon: Upload,\n path: '/files',\n color: 'text-status-warning-text',\n },\n {\n id: 'settings',\n label: tWithFallback(t, 'dashboard.quickActions.settings', 'Settings'),\n icon: Cog,\n path: '/settings',\n color: 'text-accent-cyan',\n },\n ];\n}\n\nfunction fluidgridsQuickActions(t: Translate): QuickAction[] {\n return [\n {\n id: 'new-workflow',\n label: tWithFallback(t, 'dashboard.quickActions.newWorkflow', 'New Workflow'),\n icon: GitBranch,\n path: '/workflows/new',\n color: 'text-accent-blue',\n },\n {\n id: 'view-runs',\n label: tWithFallback(t, 'dashboard.quickActions.viewRuns', 'View Runs'),\n icon: PlayCircle,\n path: '/workflows/runs',\n color: 'text-status-success-text',\n },\n {\n id: 'browse-templates',\n label: tWithFallback(t, 'dashboard.quickActions.templates', 'Templates'),\n icon: LayoutTemplate,\n path: '/workflows/templates',\n color: 'text-accent-purple',\n },\n {\n id: 'invite-member',\n label: tWithFallback(t, 'dashboard.quickActions.inviteMember', 'Invite Member'),\n icon: UserPlus,\n path: '/workspaces/members',\n color: 'text-accent-pink',\n },\n {\n id: 'upload-file',\n label: tWithFallback(t, 'dashboard.quickActions.uploadFile', 'Upload File'),\n icon: Upload,\n path: '/files',\n color: 'text-status-warning-text',\n },\n {\n id: 'settings',\n label: tWithFallback(t, 'dashboard.quickActions.settings', 'Settings'),\n icon: Cog,\n path: '/settings',\n color: 'text-accent-cyan',\n },\n ];\n}\n\nfunction pickActions(t: Translate, productId?: string): QuickAction[] {\n if (productId === 'vibecontrols') return vibecontrolsQuickActions(t);\n if (productId === 'fluidgrids') return fluidgridsQuickActions(t);\n return workspacesQuickActions(t);\n}\n\nexport function QuickActionsWidget({ navigate, isLoading, productId }: WidgetProps) {\n const { t } = useI18n();\n const actions = pickActions(t, productId);\n return (\n <WidgetWrapper\n title={tWithFallback(t, 'dashboard.quickActions.title', 'Quick Actions')}\n icon={<Zap className=\"size-4\" />}\n size=\"medium\"\n isLoading={isLoading}\n >\n <div className=\"grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-2\">\n {actions.map((action) => (\n <Button\n key={action.id}\n variant=\"outline\"\n className=\"flex flex-col items-center gap-1.5 h-auto py-3 px-2 text-xs hover:bg-accent/5 transition-colors\"\n onClick={() => navigate(action.path)}\n >\n <action.icon className={`size-5 ${action.color}`} />\n <span className=\"text-text-secondary font-medium\">{action.label}</span>\n </Button>\n ))}\n </div>\n </WidgetWrapper>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"RecentActivityWidget.js","names":[],"sources":["../../../../src/pages/dashboard/widgets/RecentActivityWidget.tsx"],"sourcesContent":["import { Activity, ArrowRight } from 'lucide-react';\nimport { Button, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport type { I18nContextValue } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { WidgetWrapper } from '../WidgetWrapper';\nimport { tWithFallback } from '../../../utils/i18n';\nimport type { WidgetProps } from '../types';\nimport { formatRelativeTime } from '@burdenoff/fe-libs/shared/utils';\n\n/**\n * Human-readable label for an audit action, resolved through i18n with\n * **literal** keys.\n *\n * This used to be a `Record<string, string>` of English labels. A record\n * lookup can only ever produce a *computed* key, and the key extractor\n * (fluidgrids-app/scripts/extract-i18n-keys.ts) collects string literals only\n * — so those labels could never be extracted or seeded, and would render in\n * English in every locale forever. Spelling each action out gives the\n * extractor something to find. Unknown actions return `undefined`, exactly as\n * the record lookup did, so the caller still falls back to the raw action id.\n * [BOFF-5997]\n */\nfunction resolveActionLabel(t: I18nContextValue['t'], action: string): string | undefined {\n switch (action) {\n case 'workspace.created':\n return tWithFallback(t, 'dashboard.recentActivity.workspaceCreated', 'Created workspace');\n case 'workspace.updated':\n return tWithFallback(t, 'dashboard.recentActivity.workspaceUpdated', 'Updated workspace');\n case 'project.created':\n return tWithFallback(t, 'dashboard.recentActivity.projectCreated', 'Created project');\n case 'project.updated':\n return tWithFallback(t, 'dashboard.recentActivity.projectUpdated', 'Updated project');\n case 'member.added':\n return tWithFallback(t, 'dashboard.recentActivity.memberAdded', 'Added member');\n case 'member.removed':\n return tWithFallback(t, 'dashboard.recentActivity.memberRemoved', 'Removed member');\n case 'file.uploaded':\n return tWithFallback(t, 'dashboard.recentActivity.fileUploaded', 'Uploaded file');\n case 'tag.created':\n return tWithFallback(t, 'dashboard.recentActivity.tagCreated', 'Created tag');\n case 'tag.assigned':\n return tWithFallback(t, 'dashboard.recentActivity.tagAssigned', 'Assigned tag');\n case 'workflow.started':\n return tWithFallback(t, 'dashboard.recentActivity.workflowStarted', 'Started workflow');\n case 'workflow.completed':\n return tWithFallback(t, 'dashboard.recentActivity.workflowCompleted', 'Completed workflow');\n case 'event.created':\n return tWithFallback(t, 'dashboard.recentActivity.eventCreated', 'Created event');\n case 'comment.added':\n return tWithFallback(t, 'dashboard.recentActivity.commentAdded', 'Added comment');\n default:\n return undefined;\n }\n}\n\nconst entityIcons: Record<string, string> = {\n workspace: 'bg-status-info-bg-subtle text-status-info-text',\n project: 'bg-status-success-bg-subtle text-status-success-text',\n member: 'bg-accent-purple-subtle text-accent-purple',\n file: 'bg-status-warning-bg-subtle text-status-warning-text',\n tag: 'bg-accent-cyan-subtle text-accent-cyan',\n workflow: 'bg-accent-orange-subtle text-accent-orange',\n calendar: 'bg-accent-pink-subtle text-accent-pink',\n};\n\nexport function RecentActivityWidget({ workspaceData, isLoading, navigate }: WidgetProps) {\n const { t } = useI18n();\n const activities = workspaceData?.recentActivities ?? [];\n\n return (\n <WidgetWrapper\n title={tWithFallback(t, 'dashboard.recentActivity.title', 'Recent Activity')}\n icon={<Activity className=\"size-4\" />}\n size=\"large\"\n isLoading={isLoading}\n unavailable={!workspaceData}\n headerAction={\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"text-xs\"\n onClick={() => navigate('/workspaces/activity')}\n >\n {tWithFallback(t, 'dashboard.recentActivity.viewAll', 'View all')}{' '}\n <ArrowRight className=\"ml-1 size-3\" />\n </Button>\n }\n >\n {activities.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={tWithFallback(t, 'dashboard.recentActivity.emptyTitle', 'No recent activity')}\n description={tWithFallback(\n t,\n 'dashboard.recentActivity.emptyDescription',\n 'Activity will appear here as you use the platform'\n )}\n className=\"py-6\"\n />\n ) : (\n <div className=\"space-y-1\">\n {activities.map((activity, idx) => {\n const resourceType = activity.resourceType || 'workspace';\n const colorClass = entityIcons[resourceType] || 'bg-bg-sunken text-text-secondary';\n const timeAgo = formatRelativeTime(new Date(activity.timestamp));\n const actionLabel = resolveActionLabel(t, activity.action);\n return (\n <div\n key={activity.id || idx}\n className=\"flex items-start gap-3 p-2 rounded-md hover:bg-accent/5 transition-colors\"\n >\n <div\n className={`rounded-full size-7 flex items-center justify-center text-xs font-bold shrink-0 ${colorClass}`}\n >\n {resourceType.charAt(0).toUpperCase()}\n </div>\n <div className=\"min-w-0 flex-1\">\n <p className=\"text-sm text-text-primary truncate\">\n {activity.description || actionLabel || activity.action}\n </p>\n <p className=\"text-xs text-text-secondary\">{timeAgo}</p>\n </div>\n </div>\n );\n })}\n </div>\n )}\n </WidgetWrapper>\n );\n}\n"],"mappings":";;;;;;;;AAsBA,SAAS,EAAmB,GAA0B,GAAoC;AACxF,SAAQ,GAAR;EACE,KAAK,oBACH,QAAO,EAAc,GAAG,6CAA6C,oBAAoB;EAC3F,KAAK,oBACH,QAAO,EAAc,GAAG,6CAA6C,oBAAoB;EAC3F,KAAK,kBACH,QAAO,EAAc,GAAG,2CAA2C,kBAAkB;EACvF,KAAK,kBACH,QAAO,EAAc,GAAG,2CAA2C,kBAAkB;EACvF,KAAK,eACH,QAAO,EAAc,GAAG,wCAAwC,eAAe;EACjF,KAAK,iBACH,QAAO,EAAc,GAAG,0CAA0C,iBAAiB;EACrF,KAAK,gBACH,QAAO,EAAc,GAAG,yCAAyC,gBAAgB;EACnF,KAAK,cACH,QAAO,EAAc,GAAG,uCAAuC,cAAc;EAC/E,KAAK,eACH,QAAO,EAAc,GAAG,wCAAwC,eAAe;EACjF,KAAK,mBACH,QAAO,EAAc,GAAG,4CAA4C,mBAAmB;EACzF,KAAK,qBACH,QAAO,EAAc,GAAG,8CAA8C,qBAAqB;EAC7F,KAAK,gBACH,QAAO,EAAc,GAAG,yCAAyC,gBAAgB;EACnF,KAAK,gBACH,QAAO,EAAc,GAAG,yCAAyC,gBAAgB;EACnF,QACE;;;AAIN,IAAM,IAAsC;CAC1C,WAAW;CACX,SAAS;CACT,QAAQ;CACR,MAAM;CACN,KAAK;CACL,UAAU;CACV,UAAU;CACX;AAED,SAAgB,EAAqB,EAAE,kBAAe,cAAW,eAAyB;CACxF,IAAM,EAAE,SAAM,GAAS,EACjB,IAAa,GAAe,oBAAoB,EAAE;AAExD,QACE,kBAAC,GAAD;EACE,OAAO,EAAc,GAAG,kCAAkC,kBAAkB;EAC5E,MAAM,kBAAC,GAAD,EAAU,WAAU,UAAW,CAAA;EACrC,MAAK;EACM;EACX,aAAa,CAAC;EACd,cACE,kBAAC,GAAD;GACE,SAAQ;GACR,MAAK;GACL,WAAU;GACV,eAAe,EAAS,uBAAuB;aAJjD;IAMG,EAAc,GAAG,oCAAoC,WAAW;IAAE;IACnE,kBAAC,GAAD,EAAY,WAAU,eAAgB,CAAA;IAC/B;;YAGV,EAAW,WAAW,IACrB,kBAAC,GAAD;GACE,cAAa;GACb,OAAO,EAAc,GAAG,uCAAuC,qBAAqB;GACpF,aAAa,EACX,GACA,6CACA,oDACD;GACD,WAAU;GACV,CAAA,GAEF,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAW,KAAK,GAAU,MAAQ;IACjC,IAAM,IAAe,EAAS,gBAAgB,aACxC,IAAa,EAAY,MAAiB,oCAC1C,IAAU,EAAmB,IAAI,KAAK,EAAS,UAAU,CAAC,EAC1D,IAAc,EAAmB,GAAG,EAAS,OAAO;AAC1D,WACE,kBAAC,OAAD;KAEE,WAAU;eAFZ,CAIE,kBAAC,OAAD;MACE,WAAW,mFAAmF;gBAE7F,EAAa,OAAO,EAAE,CAAC,aAAa;MACjC,CAAA,EACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAS,eAAe,KAAe,EAAS;OAC/C,CAAA,EACJ,kBAAC,KAAD;OAAG,WAAU;iBAA+B;OAAY,CAAA,CACpD;QACF;OAdC,EAAS,MAAM,EAchB;KAER;GACE,CAAA;EAEM,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"SessionsWidget.js","names":[],"sources":["../../../../src/pages/dashboard/widgets/SessionsWidget.tsx"],"sourcesContent":["import { Terminal, ArrowRight } from 'lucide-react';\nimport { Badge, Button, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport type { I18nContextValue } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { WidgetWrapper } from '../WidgetWrapper';\nimport { tWithFallback } from '../../../utils/i18n';\nimport type { WidgetProps } from '../types';\n\nconst STATUS_BADGE: Record<string, string> = {\n RUNNING: 'bg-status-success-bg text-status-success-text',\n ACTIVE: 'bg-status-success-bg text-status-success-text',\n STARTING: 'bg-status-warning-bg text-status-warning-text',\n STOPPED: 'bg-text-tertiary/20 text-text-tertiary',\n ERROR: 'bg-status-error-bg text-status-error-text',\n};\n\n// Live statuses get a subtle opacity pulse (reduced-motion-safe, no movement).\nconst LIVE_STATUSES = new Set(['RUNNING', 'ACTIVE']);\n\n/**\n * Badge copy for a session status, resolved through i18n with **literal**\n * keys — a lookup table would force a computed key, which the key extractor\n * (fluidgrids-app/scripts/extract-i18n-keys.ts) cannot see and the seeder can\n * therefore never ship. Unknown statuses keep the previous behaviour of\n * lower-casing the raw enum value. [BOFF-5997]\n */\nfunction resolveStatusLabel(t: I18nContextValue['t'], status: string): string {\n switch (status) {\n case 'RUNNING':\n return tWithFallback(t, 'dashboard.sessions.statusRunning', 'running');\n case 'ACTIVE':\n return tWithFallback(t, 'dashboard.sessions.statusActive', 'active');\n case 'STARTING':\n return tWithFallback(t, 'dashboard.sessions.statusStarting', 'starting');\n case 'STOPPED':\n return tWithFallback(t, 'dashboard.sessions.statusStopped', 'stopped');\n case 'ERROR':\n return tWithFallback(t, 'dashboard.sessions.statusError', 'error');\n default:\n return status.toLowerCase();\n }\n}\n\nfunction formatRelative(t: I18nContextValue['t'], dateString: string | null | undefined): string {\n if (!dateString) return '';\n const diffMs = Date.now() - new Date(dateString).getTime();\n const diffMin = Math.floor(diffMs / 60000);\n if (diffMin < 1) return tWithFallback(t, 'dashboard.sessions.justNow', 'just now');\n if (diffMin < 60)\n return tWithFallback(t, 'dashboard.sessions.minutesAgo', '{count}m', { count: diffMin });\n const diffHr = Math.floor(diffMin / 60);\n if (diffHr < 24)\n return tWithFallback(t, 'dashboard.sessions.hoursAgo', '{count}h', { count: diffHr });\n return new Date(dateString).toLocaleDateString(undefined, {\n month: 'short',\n day: 'numeric',\n });\n}\n\nexport function SessionsWidget({ workspaceData, isLoading, navigate }: WidgetProps) {\n const { t } = useI18n();\n const sessions = workspaceData?.activeSessions ?? [];\n\n return (\n <WidgetWrapper\n title={tWithFallback(t, 'dashboard.sessions.title', 'Active Sessions')}\n icon={<Terminal className=\"size-4\" />}\n size=\"medium\"\n isLoading={isLoading}\n unavailable={!workspaceData}\n headerAction={\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"text-xs\"\n onClick={() => navigate('/vibecontrols/sessions')}\n >\n {tWithFallback(t, 'dashboard.sessions.viewAll', 'View all')}{' '}\n <ArrowRight className=\"ml-1 size-3\" />\n </Button>\n }\n >\n {sessions.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-sessions\"\n title={tWithFallback(t, 'dashboard.sessions.emptyTitle', 'No active sessions')}\n className=\"py-6\"\n />\n ) : (\n <ul className=\"space-y-1.5\">\n {sessions.slice(0, 5).map((s) => (\n <li key={s.id}>\n <button\n type=\"button\"\n onClick={() => navigate(`/vibecontrols/sessions/${s.id}`)}\n className=\"flex w-full items-center justify-between gap-2 rounded-md p-2 text-left transition-colors hover:bg-accent/5\"\n >\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {s.name ||\n tWithFallback(t, 'dashboard.sessions.unnamedSession', 'Session {id}', {\n id: s.id.slice(0, 8),\n })}\n </p>\n {s.agent?.name ? (\n <p className=\"truncate text-[11px] text-text-secondary\">{s.agent.name}</p>\n ) : null}\n </div>\n <div className=\"flex flex-col items-end gap-1\">\n <Badge\n variant=\"outline\"\n className={`text-[10px] ${STATUS_BADGE[s.status] ?? ''} ${\n LIVE_STATUSES.has(s.status) ? 'motion-safe:animate-vc-glow-pulse' : ''\n }`}\n >\n {resolveStatusLabel(t, s.status)}\n </Badge>\n {s.startedAt ? (\n <span className=\"text-[10px] text-text-secondary\">\n {formatRelative(t, s.startedAt)}\n </span>\n ) : null}\n </div>\n </button>\n </li>\n ))}\n </ul>\n )}\n </WidgetWrapper>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"StorageWidget.js","names":[],"sources":["../../../../src/pages/dashboard/widgets/StorageWidget.tsx"],"sourcesContent":["import { HardDrive } from 'lucide-react';\nimport { useUpgradePrompt } from '@burdenoff/fe-libs/shared/hooks';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport type { I18nContextValue } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { WidgetWrapper } from '../WidgetWrapper';\nimport { tWithFallback } from '../../../utils/i18n';\nimport type { WidgetProps } from '../types';\n\nconst FREE_TIER_TOTAL_BYTES = 50 * 1024 * 1024; // 50 MB\nconst PAID_TIER_TOTAL_BYTES = 5 * 1024 * 1024 * 1024; // 5 GB\n\n/**\n * Byte-size unit symbols, each translated through a **literal** key — an array\n * of keys indexed by magnitude would hand the translator a computed key, which\n * the extractor (fluidgrids-app/scripts/extract-i18n-keys.ts) cannot see and\n * the seeder can therefore never ship. Order is load-bearing: the index is the\n * power of 1024. [BOFF-5997]\n */\nfunction byteUnits(t: I18nContextValue['t']): string[] {\n return [\n tWithFallback(t, 'dashboard.storage.unitBytes', 'B'),\n tWithFallback(t, 'dashboard.storage.unitKilobytes', 'KB'),\n tWithFallback(t, 'dashboard.storage.unitMegabytes', 'MB'),\n tWithFallback(t, 'dashboard.storage.unitGigabytes', 'GB'),\n tWithFallback(t, 'dashboard.storage.unitTerabytes', 'TB'),\n ];\n}\n\nfunction formatBytes(t: I18nContextValue['t'], bytes: number): string {\n if (bytes === 0) {\n // For 0 we want to render the unit the user will see for the total\n // (e.g. \"0 MB / 50 MB\"), so let the caller pick a unit explicitly.\n return `0 ${tWithFallback(t, 'dashboard.storage.unitBytes', 'B')}`;\n }\n const k = 1024;\n const sizes = byteUnits(t);\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;\n}\n\n/**\n * Pick the display unit of the *total* so the \"used / total\" line is\n * consistent (e.g. \"0 MB / 50 MB\" not \"0 B / 50 MB\").\n */\nfunction formatBytesAs(t: I18nContextValue['t'], bytes: number, totalBytes: number): string {\n const k = 1024;\n const sizes = byteUnits(t);\n if (totalBytes <= 0) return formatBytes(t, bytes);\n const totalIdx = Math.min(Math.floor(Math.log(totalBytes) / Math.log(k)), sizes.length - 1);\n const value = bytes / Math.pow(k, totalIdx);\n // Render as integer when exact (e.g. 0, 50); else one decimal.\n const rendered = Number.isInteger(value) ? value.toString() : value.toFixed(1);\n return `${rendered} ${sizes[totalIdx]}`;\n}\n\nexport function StorageWidget({ workspaceData, isLoading }: WidgetProps) {\n const { t } = useI18n();\n const { isFree } = useUpgradePrompt({ resource: 'storage' });\n const storage = workspaceData?.storageUsage;\n\n // Fall back to plan-tier defaults when the backend hasn't reported usage\n // yet. Storage is core, so \"metrics unavailable\" is not an acceptable UX.\n const fallbackTotalBytes = isFree ? FREE_TIER_TOTAL_BYTES : PAID_TIER_TOTAL_BYTES;\n const usedBytes = storage?.usedBytes ?? 0;\n const totalBytes =\n storage?.totalBytes && storage.totalBytes > 0 ? storage.totalBytes : fallbackTotalBytes;\n const percentage = totalBytes > 0 ? Math.min((usedBytes / totalBytes) * 100, 100) : 0;\n const barColor =\n percentage > 80\n ? 'bg-status-error-bg'\n : percentage > 60\n ? 'bg-status-warning-bg'\n : 'bg-status-success-bg';\n\n return (\n <WidgetWrapper\n title={tWithFallback(t, 'dashboard.storage.title', 'Storage')}\n icon={<HardDrive className=\"size-4\" />}\n size=\"small\"\n isLoading={isLoading}\n >\n <div className=\"space-y-3\">\n <div className=\"space-y-1.5\">\n <div className=\"flex justify-between text-xs\">\n <span className=\"text-text-secondary\">\n {tWithFallback(t, 'dashboard.storage.used', 'Used')}\n </span>\n <span className=\"font-medium text-text-primary\">\n {tWithFallback(t, 'dashboard.storage.usedOfTotal', '{used} / {total}', {\n used: formatBytesAs(t, usedBytes, totalBytes),\n total: formatBytes(t, totalBytes),\n })}\n </span>\n </div>\n <div className=\"h-2 w-full bg-bg-canvas rounded-full overflow-hidden\">\n <div\n className={`h-full rounded-full ${barColor} transition-all duration-500`}\n style={{ width: `${percentage}%` }}\n />\n </div>\n <p className=\"text-[11px] text-text-secondary text-right\">\n {tWithFallback(t, 'dashboard.storage.percentUsed', '{percent}% used', {\n percent: percentage.toFixed(1),\n })}\n </p>\n </div>\n {storage?.fileCount != null && (\n <div className=\"flex items-center justify-between text-xs\">\n <span className=\"text-text-secondary\">\n {tWithFallback(t, 'dashboard.storage.totalFiles', 'Total files')}\n </span>\n <span className=\"font-medium text-text-primary\">\n {storage.fileCount.toLocaleString()}\n </span>\n </div>\n )}\n </div>\n </WidgetWrapper>\n );\n}\n"],"mappings":";;;;;;;AAQA,IAAM,IAAwB,KAAK,OAAO,MACpC,IAAwB,IAAI,OAAO,OAAO;AAShD,SAAS,EAAU,GAAoC;AACrD,QAAO;EACL,EAAc,GAAG,+BAA+B,IAAI;EACpD,EAAc,GAAG,mCAAmC,KAAK;EACzD,EAAc,GAAG,mCAAmC,KAAK;EACzD,EAAc,GAAG,mCAAmC,KAAK;EACzD,EAAc,GAAG,mCAAmC,KAAK;EAC1D;;AAGH,SAAS,EAAY,GAA0B,GAAuB;AACpE,KAAI,MAAU,EAGZ,QAAO,KAAK,EAAc,GAAG,+BAA+B,IAAI;CAElE,IAAM,IAAI,MACJ,IAAQ,EAAU,EAAE,EACpB,IAAI,KAAK,MAAM,KAAK,IAAI,EAAM,GAAG,KAAK,IAAI,EAAE,CAAC;AACnD,QAAO,GAAG,YAAY,IAAiB,MAAG,GAAI,QAAQ,EAAE,CAAC,CAAC,GAAG,EAAM;;AAOrE,SAAS,EAAc,GAA0B,GAAe,GAA4B;CAC1F,IAAM,IAAI,MACJ,IAAQ,EAAU,EAAE;AAC1B,KAAI,KAAc,EAAG,QAAO,EAAY,GAAG,EAAM;CACjD,IAAM,IAAW,KAAK,IAAI,KAAK,MAAM,KAAK,IAAI,EAAW,GAAG,KAAK,IAAI,EAAE,CAAC,EAAE,EAAM,SAAS,EAAE,EACrF,IAAQ,IAAiB,MAAG;AAGlC,QAAO,GADU,OAAO,UAAU,EAAM,GAAG,EAAM,UAAU,GAAG,EAAM,QAAQ,EAAE,CAC3D,GAAG,EAAM;;AAG9B,SAAgB,EAAc,EAAE,kBAAe,gBAA0B;CACvE,IAAM,EAAE,SAAM,GAAS,EACjB,EAAE,cAAW,EAAiB,EAAE,UAAU,WAAW,CAAC,EACtD,IAAU,GAAe,cAIzB,IAAqB,IAAS,IAAwB,GACtD,IAAY,GAAS,aAAa,GAClC,IACJ,GAAS,cAAc,EAAQ,aAAa,IAAI,EAAQ,aAAa,GACjE,IAAa,IAAa,IAAI,KAAK,IAAK,IAAY,IAAc,KAAK,IAAI,GAAG,GAC9E,IACJ,IAAa,KACT,uBACA,IAAa,KACX,yBACA;AAER,QACE,kBAAC,GAAD;EACE,OAAO,EAAc,GAAG,2BAA2B,UAAU;EAC7D,MAAM,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA;EACtC,MAAK;EACM;YAEX,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAc,GAAG,0BAA0B,OAAO;OAC9C,CAAA,EACP,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAc,GAAG,iCAAiC,oBAAoB;QACrE,MAAM,EAAc,GAAG,GAAW,EAAW;QAC7C,OAAO,EAAY,GAAG,EAAW;QAClC,CAAC;OACG,CAAA,CACH;;KACN,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,OAAD;OACE,WAAW,uBAAuB,EAAS;OAC3C,OAAO,EAAE,OAAO,GAAG,EAAW,IAAI;OAClC,CAAA;MACE,CAAA;KACN,kBAAC,KAAD;MAAG,WAAU;gBACV,EAAc,GAAG,iCAAiC,mBAAmB,EACpE,SAAS,EAAW,QAAQ,EAAE,EAC/B,CAAC;MACA,CAAA;KACA;OACL,GAAS,aAAa,QACrB,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAU;eACb,EAAc,GAAG,gCAAgC,cAAc;KAC3D,CAAA,EACP,kBAAC,QAAD;KAAM,WAAU;eACb,EAAQ,UAAU,gBAAgB;KAC9B,CAAA,CACH;MAEJ;;EACQ,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"TagCloudWidget.js","names":[],"sources":["../../../../src/pages/dashboard/widgets/TagCloudWidget.tsx"],"sourcesContent":["import { Tags, ArrowRight } from 'lucide-react';\nimport { Badge, Button, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { WidgetWrapper } from '../WidgetWrapper';\nimport type { WidgetProps } from '../types';\nimport { tWithFallback } from '../../../utils/i18n';\n\nconst tagColors = [\n 'bg-status-info-bg-subtle text-status-info-text border-border-default',\n 'bg-status-success-bg-subtle text-status-success-text border-border-default',\n 'bg-accent-purple-subtle text-accent-purple border-border-default',\n 'bg-status-warning-bg-subtle text-status-warning-text border-border-default',\n 'bg-accent-pink-subtle text-accent-pink border-border-default',\n 'bg-accent-cyan-subtle text-accent-cyan border-border-default',\n 'bg-accent-orange-subtle text-accent-orange border-border-default',\n 'bg-accent-rose-subtle text-accent-rose border-border-default',\n];\n\nexport function TagCloudWidget({ workspaceData, isLoading, navigate }: WidgetProps) {\n const { t } = useI18n();\n const tags = workspaceData?.tags.items ?? [];\n const total = workspaceData?.tags.total ?? 0;\n\n return (\n <WidgetWrapper\n title={tWithFallback(t, 'dashboard.tagCloud.title', 'Tags')}\n icon={<Tags className=\"size-4\" />}\n size=\"small\"\n isLoading={isLoading}\n unavailable={!workspaceData}\n headerAction={\n <Button variant=\"ghost\" size=\"sm\" className=\"text-xs\" onClick={() => navigate('/tags')}>\n <ArrowRight className=\"size-3\" />\n </Button>\n }\n >\n {tags.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title={tWithFallback(t, 'dashboard.tagCloud.emptyTitle', 'No tags created yet')}\n description={tWithFallback(\n t,\n 'dashboard.tagCloud.emptyDescription',\n 'Tags help you label and filter projects and content across the workspace.'\n )}\n className=\"py-4\"\n />\n ) : (\n <div className=\"space-y-2\">\n <div className=\"flex flex-wrap gap-1.5\">\n {tags.slice(0, 12).map((tag, idx) => (\n <Badge\n key={tag.id}\n variant=\"outline\"\n className={`text-[11px] cursor-pointer hover:opacity-80 transition-opacity ${tagColors[idx % tagColors.length]}`}\n onClick={() => navigate('/tags')}\n >\n {tag.label}\n </Badge>\n ))}\n </div>\n {total > 12 && (\n <p className=\"text-[11px] text-text-secondary text-center\">\n {tWithFallback(t, 'dashboard.tagCloud.more', '+{count} more tags', {\n count: total - 12,\n })}\n </p>\n )}\n </div>\n )}\n </WidgetWrapper>\n );\n}\n"],"mappings":";;;;;;;AAOA,IAAM,IAAY;CAChB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAED,SAAgB,EAAe,EAAE,kBAAe,cAAW,eAAyB;CAClF,IAAM,EAAE,SAAM,GAAS,EACjB,IAAO,GAAe,KAAK,SAAS,EAAE,EACtC,IAAQ,GAAe,KAAK,SAAS;AAE3C,QACE,kBAAC,GAAD;EACE,OAAO,EAAc,GAAG,4BAA4B,OAAO;EAC3D,MAAM,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA;EACjC,MAAK;EACM;EACX,aAAa,CAAC;EACd,cACE,kBAAC,GAAD;GAAQ,SAAQ;GAAQ,MAAK;GAAK,WAAU;GAAU,eAAe,EAAS,QAAQ;aACpF,kBAAC,GAAD,EAAY,WAAU,UAAW,CAAA;GAC1B,CAAA;YAGV,EAAK,WAAW,IACf,kBAAC,GAAD;GACE,cAAa;GACb,OAAO,EAAc,GAAG,iCAAiC,sBAAsB;GAC/E,aAAa,EACX,GACA,uCACA,4EACD;GACD,WAAU;GACV,CAAA,GAEF,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAK,MAAM,GAAG,GAAG,CAAC,KAAK,GAAK,MAC3B,kBAAC,GAAD;KAEE,SAAQ;KACR,WAAW,kEAAkE,EAAU,IAAM,EAAU;KACvG,eAAe,EAAS,QAAQ;eAE/B,EAAI;KACC,EAND,EAAI,GAMH,CACR;IACE,CAAA,EACL,IAAQ,MACP,kBAAC,KAAD;IAAG,WAAU;cACV,EAAc,GAAG,2BAA2B,sBAAsB,EACjE,OAAO,IAAQ,IAChB,CAAC;IACA,CAAA,CAEF;;EAEM,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"TipsWidget.js","names":[],"sources":["../../../../src/pages/dashboard/widgets/TipsWidget.tsx"],"sourcesContent":["import type { LucideIcon } from 'lucide-react';\nimport {\n Lightbulb,\n ExternalLink,\n BookOpen,\n Rocket,\n Shield,\n Workflow,\n Users,\n Puzzle,\n Newspaper,\n Sparkles,\n GitBranch,\n LayoutTemplate,\n ScrollText,\n} from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { WidgetWrapper } from '../WidgetWrapper';\nimport type { WidgetProps } from '../types';\nimport { tWithFallback } from '../../../utils/i18n';\n\ntype Translator = Parameters<typeof tWithFallback>[0];\n\ninterface Tip {\n icon: LucideIcon;\n title: string;\n description: string;\n href: string;\n external?: boolean;\n color: string;\n}\n\nfunction getVibecontrolsTips(t: Translator): Tip[] {\n return [\n {\n icon: BookOpen,\n title: tWithFallback(t, 'dashboard.tips.vibecontrols.readDocs.title', 'Read the docs'),\n description: tWithFallback(\n t,\n 'dashboard.tips.vibecontrols.readDocs.description',\n 'Guides, references, and tutorials at docs.vibecontrols.com.'\n ),\n href: 'https://docs.vibecontrols.com',\n external: true,\n color: 'text-accent-blue',\n },\n {\n icon: Users,\n title: tWithFallback(t, 'dashboard.tips.vibecontrols.community.title', 'Join the community'),\n description: tWithFallback(\n t,\n 'dashboard.tips.vibecontrols.community.description',\n 'Ask questions, share workflows, and chat with the team.'\n ),\n href: 'https://community.vibecontrols.com',\n external: true,\n color: 'text-accent-purple',\n },\n {\n icon: Puzzle,\n title: tWithFallback(t, 'dashboard.tips.vibecontrols.plugins.title', 'Browse plugins'),\n description: tWithFallback(\n t,\n 'dashboard.tips.vibecontrols.plugins.description',\n '50+ plugins for terminals, AI, tunnels, sandboxes, and more.'\n ),\n href: '/vibecontrols/plugins',\n color: 'text-status-success-text',\n },\n {\n icon: Newspaper,\n title: tWithFallback(t, 'dashboard.tips.vibecontrols.blog.title', 'Latest from the blog'),\n description: tWithFallback(\n t,\n 'dashboard.tips.vibecontrols.blog.description',\n 'Product updates, deep dives, and engineering posts.'\n ),\n href: 'https://vibecontrols.com/blog',\n external: true,\n color: 'text-status-warning-text',\n },\n {\n icon: Sparkles,\n title: tWithFallback(\n t,\n 'dashboard.tips.vibecontrols.featurePreviews.title',\n 'Feature previews'\n ),\n description: tWithFallback(\n t,\n 'dashboard.tips.vibecontrols.featurePreviews.description',\n 'Try opt-in features before they ship to everyone.'\n ),\n href: '/feature-previews',\n color: 'text-accent-pink',\n },\n ];\n}\n\nfunction getWorkspacesTips(t: Translator): Tip[] {\n return [\n {\n icon: Rocket,\n title: tWithFallback(\n t,\n 'dashboard.tips.workspaces.createWorkspace.title',\n 'Create your first workspace'\n ),\n description: tWithFallback(\n t,\n 'dashboard.tips.workspaces.createWorkspace.description',\n 'Organize your team with a dedicated workspace.'\n ),\n href: '/workspaces/list',\n color: 'text-accent-blue',\n },\n {\n icon: Workflow,\n title: tWithFallback(\n t,\n 'dashboard.tips.workspaces.automateWorkflows.title',\n 'Automate with workflows'\n ),\n description: tWithFallback(\n t,\n 'dashboard.tips.workspaces.automateWorkflows.description',\n 'Build automation workflows to streamline your processes.'\n ),\n href: '/workflows',\n color: 'text-status-success-text',\n },\n {\n icon: Shield,\n title: tWithFallback(t, 'dashboard.tips.workspaces.permissions.title', 'Set up permissions'),\n description: tWithFallback(\n t,\n 'dashboard.tips.workspaces.permissions.description',\n 'Configure role-based access for your workspace members.'\n ),\n href: '/rbac',\n color: 'text-accent-purple',\n },\n {\n icon: BookOpen,\n title: tWithFallback(t, 'dashboard.tips.workspaces.exploreApi.title', 'Explore the API'),\n description: tWithFallback(\n t,\n 'dashboard.tips.workspaces.exploreApi.description',\n 'Check out our developer portal for API documentation.'\n ),\n href: '/devportal',\n color: 'text-status-warning-text',\n },\n ];\n}\n\nfunction getFluidgridsTips(t: Translator): Tip[] {\n return [\n {\n icon: GitBranch,\n title: tWithFallback(\n t,\n 'dashboard.tips.fluidgrids.buildWorkflow.title',\n 'Build your first workflow'\n ),\n description: tWithFallback(\n t,\n 'dashboard.tips.fluidgrids.buildWorkflow.description',\n 'Drag nodes onto the canvas to automate a process end to end.'\n ),\n href: '/workflows/new',\n color: 'text-accent-blue',\n },\n {\n icon: LayoutTemplate,\n title: tWithFallback(t, 'dashboard.tips.fluidgrids.template.title', 'Start from a template'),\n description: tWithFallback(\n t,\n 'dashboard.tips.fluidgrids.template.description',\n 'Skip the blank canvas with a ready-made workflow template.'\n ),\n href: '/workflows/templates',\n color: 'text-status-success-text',\n },\n {\n icon: ScrollText,\n title: tWithFallback(t, 'dashboard.tips.fluidgrids.debugRun.title', 'Debug a failed run'),\n description: tWithFallback(\n t,\n 'dashboard.tips.fluidgrids.debugRun.description',\n 'Inspect node-by-node execution logs for any workflow run.'\n ),\n href: '/workflows/runs',\n color: 'text-accent-purple',\n },\n {\n icon: BookOpen,\n title: tWithFallback(t, 'dashboard.tips.fluidgrids.readDocs.title', 'Read the docs'),\n description: tWithFallback(\n t,\n 'dashboard.tips.fluidgrids.readDocs.description',\n 'Guides and node reference at docs.fluidgrids.com.'\n ),\n href: 'https://docs.fluidgrids.com',\n external: true,\n color: 'text-status-warning-text',\n },\n {\n icon: Users,\n title: tWithFallback(t, 'dashboard.tips.fluidgrids.community.title', 'Join the community'),\n description: tWithFallback(\n t,\n 'dashboard.tips.fluidgrids.community.description',\n 'Share workflows and get help from other builders.'\n ),\n href: 'https://community.fluidgrids.com',\n external: true,\n color: 'text-accent-pink',\n },\n ];\n}\n\nfunction pickTips(t: Translator, productId?: string): Tip[] {\n if (productId === 'vibecontrols') return getVibecontrolsTips(t);\n if (productId === 'fluidgrids') return getFluidgridsTips(t);\n return getWorkspacesTips(t);\n}\n\nexport function TipsWidget({ navigate, isLoading, productId }: WidgetProps) {\n const { t } = useI18n();\n const tips = pickTips(t, productId);\n\n return (\n <WidgetWrapper\n title={tWithFallback(t, 'dashboard.tips.title', 'Tips & Resources')}\n icon={<Lightbulb className=\"size-4\" />}\n size=\"small\"\n isLoading={isLoading}\n >\n <div className=\"space-y-2\">\n {tips.map((tip) =>\n tip.external ? (\n <a\n key={tip.title}\n href={tip.href}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"w-full flex items-start gap-2 p-1.5 rounded hover:bg-accent/5 transition-colors text-left\"\n >\n <tip.icon className={`size-4 mt-0.5 shrink-0 ${tip.color}`} />\n <div className=\"min-w-0\">\n <p className=\"text-xs font-medium text-text-primary leading-tight\">{tip.title}</p>\n <p className=\"text-[11px] text-text-secondary leading-tight mt-0.5\">\n {tip.description}\n </p>\n </div>\n <ExternalLink className=\"size-3 text-text-secondary shrink-0 mt-0.5\" />\n </a>\n ) : (\n <button\n key={tip.title}\n type=\"button\"\n className=\"w-full flex items-start gap-2 p-1.5 rounded hover:bg-accent/5 cursor-pointer transition-colors text-left\"\n onClick={() => navigate(tip.href)}\n >\n <tip.icon className={`size-4 mt-0.5 shrink-0 ${tip.color}`} />\n <div className=\"min-w-0\">\n <p className=\"text-xs font-medium text-text-primary leading-tight\">{tip.title}</p>\n <p className=\"text-[11px] text-text-secondary leading-tight mt-0.5\">\n {tip.description}\n </p>\n </div>\n </button>\n )\n )}\n </div>\n </WidgetWrapper>\n );\n}\n"],"mappings":";;;;;;AAgCA,SAAS,EAAoB,GAAsB;AACjD,QAAO;EACL;GACE,MAAM;GACN,OAAO,EAAc,GAAG,8CAA8C,gBAAgB;GACtF,aAAa,EACX,GACA,oDACA,8DACD;GACD,MAAM;GACN,UAAU;GACV,OAAO;GACR;EACD;GACE,MAAM;GACN,OAAO,EAAc,GAAG,+CAA+C,qBAAqB;GAC5F,aAAa,EACX,GACA,qDACA,0DACD;GACD,MAAM;GACN,UAAU;GACV,OAAO;GACR;EACD;GACE,MAAM;GACN,OAAO,EAAc,GAAG,6CAA6C,iBAAiB;GACtF,aAAa,EACX,GACA,mDACA,+DACD;GACD,MAAM;GACN,OAAO;GACR;EACD;GACE,MAAM;GACN,OAAO,EAAc,GAAG,0CAA0C,uBAAuB;GACzF,aAAa,EACX,GACA,gDACA,sDACD;GACD,MAAM;GACN,UAAU;GACV,OAAO;GACR;EACD;GACE,MAAM;GACN,OAAO,EACL,GACA,qDACA,mBACD;GACD,aAAa,EACX,GACA,2DACA,oDACD;GACD,MAAM;GACN,OAAO;GACR;EACF;;AAGH,SAAS,EAAkB,GAAsB;AAC/C,QAAO;EACL;GACE,MAAM;GACN,OAAO,EACL,GACA,mDACA,8BACD;GACD,aAAa,EACX,GACA,yDACA,iDACD;GACD,MAAM;GACN,OAAO;GACR;EACD;GACE,MAAM;GACN,OAAO,EACL,GACA,qDACA,0BACD;GACD,aAAa,EACX,GACA,2DACA,2DACD;GACD,MAAM;GACN,OAAO;GACR;EACD;GACE,MAAM;GACN,OAAO,EAAc,GAAG,+CAA+C,qBAAqB;GAC5F,aAAa,EACX,GACA,qDACA,0DACD;GACD,MAAM;GACN,OAAO;GACR;EACD;GACE,MAAM;GACN,OAAO,EAAc,GAAG,8CAA8C,kBAAkB;GACxF,aAAa,EACX,GACA,oDACA,wDACD;GACD,MAAM;GACN,OAAO;GACR;EACF;;AAGH,SAAS,EAAkB,GAAsB;AAC/C,QAAO;EACL;GACE,MAAM;GACN,OAAO,EACL,GACA,iDACA,4BACD;GACD,aAAa,EACX,GACA,uDACA,+DACD;GACD,MAAM;GACN,OAAO;GACR;EACD;GACE,MAAM;GACN,OAAO,EAAc,GAAG,4CAA4C,wBAAwB;GAC5F,aAAa,EACX,GACA,kDACA,6DACD;GACD,MAAM;GACN,OAAO;GACR;EACD;GACE,MAAM;GACN,OAAO,EAAc,GAAG,4CAA4C,qBAAqB;GACzF,aAAa,EACX,GACA,kDACA,4DACD;GACD,MAAM;GACN,OAAO;GACR;EACD;GACE,MAAM;GACN,OAAO,EAAc,GAAG,4CAA4C,gBAAgB;GACpF,aAAa,EACX,GACA,kDACA,oDACD;GACD,MAAM;GACN,UAAU;GACV,OAAO;GACR;EACD;GACE,MAAM;GACN,OAAO,EAAc,GAAG,6CAA6C,qBAAqB;GAC1F,aAAa,EACX,GACA,mDACA,oDACD;GACD,MAAM;GACN,UAAU;GACV,OAAO;GACR;EACF;;AAGH,SAAS,EAAS,GAAe,GAA2B;AAG1D,QAFI,MAAc,iBAAuB,EAAoB,EAAE,GAC3D,MAAc,eAAqB,EAAkB,EAAE,GACpD,EAAkB,EAAE;;AAG7B,SAAgB,EAAW,EAAE,aAAU,cAAW,gBAA0B;CAC1E,IAAM,EAAE,SAAM,GAAS,EACjB,IAAO,EAAS,GAAG,EAAU;AAEnC,QACE,kBAAC,GAAD;EACE,OAAO,EAAc,GAAG,wBAAwB,mBAAmB;EACnE,MAAM,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA;EACtC,MAAK;EACM;YAEX,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAK,KAAK,MACT,EAAI,WACF,kBAAC,KAAD;IAEE,MAAM,EAAI;IACV,QAAO;IACP,KAAI;IACJ,WAAU;cALZ;KAOE,kBAAC,EAAI,MAAL,EAAU,WAAW,0BAA0B,EAAI,SAAW,CAAA;KAC9D,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,KAAD;OAAG,WAAU;iBAAuD,EAAI;OAAU,CAAA,EAClF,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAI;OACH,CAAA,CACA;;KACN,kBAAC,GAAD,EAAc,WAAU,8CAA+C,CAAA;KACrE;MAdG,EAAI,MAcP,GAEJ,kBAAC,UAAD;IAEE,MAAK;IACL,WAAU;IACV,eAAe,EAAS,EAAI,KAAK;cAJnC,CAME,kBAAC,EAAI,MAAL,EAAU,WAAW,0BAA0B,EAAI,SAAW,CAAA,EAC9D,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,KAAD;MAAG,WAAU;gBAAuD,EAAI;MAAU,CAAA,EAClF,kBAAC,KAAD;MAAG,WAAU;gBACV,EAAI;MACH,CAAA,CACA;OACC;MAZF,EAAI,MAYF,CAEZ;GACG,CAAA;EACQ,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"UpcomingEventsWidget.js","names":[],"sources":["../../../../src/pages/dashboard/widgets/UpcomingEventsWidget.tsx"],"sourcesContent":["import { Calendar, ArrowRight, Clock } from 'lucide-react';\nimport { Badge, Button, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport type { I18nContextValue } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { WidgetWrapper } from '../WidgetWrapper';\nimport { tWithFallback } from '../../../utils/i18n';\nimport type { WidgetProps } from '../types';\n\nfunction formatEventDate(t: I18nContextValue['t'], dateStr: string): string {\n const d = new Date(dateStr);\n const now = new Date();\n const isToday = d.toDateString() === now.toDateString();\n const tomorrow = new Date(now);\n tomorrow.setDate(tomorrow.getDate() + 1);\n const isTomorrow = d.toDateString() === tomorrow.toDateString();\n\n if (isToday)\n return tWithFallback(t, 'dashboard.upcomingEvents.today', 'Today at {time}', {\n time: d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),\n });\n if (isTomorrow)\n return tWithFallback(t, 'dashboard.upcomingEvents.tomorrow', 'Tomorrow at {time}', {\n time: d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),\n });\n return d.toLocaleDateString([], {\n month: 'short',\n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit',\n });\n}\n\nexport function UpcomingEventsWidget({ workspaceData, isLoading, navigate }: WidgetProps) {\n const { t } = useI18n();\n const events = workspaceData?.upcomingEvents ?? [];\n\n return (\n <WidgetWrapper\n title={tWithFallback(t, 'dashboard.upcomingEvents.title', 'Upcoming Events')}\n icon={<Calendar className=\"size-4\" />}\n size=\"medium\"\n isLoading={isLoading}\n unavailable={!workspaceData}\n headerAction={\n <Button variant=\"ghost\" size=\"sm\" className=\"text-xs\" onClick={() => navigate('/calendar')}>\n {tWithFallback(t, 'dashboard.upcomingEvents.viewAll', 'View all')}{' '}\n <ArrowRight className=\"ml-1 size-3\" />\n </Button>\n }\n >\n {events.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={tWithFallback(t, 'dashboard.upcomingEvents.emptyTitle', 'No upcoming events')}\n className=\"py-4\"\n action={\n <Button variant=\"outline\" size=\"sm\" onClick={() => navigate('/calendar')}>\n {tWithFallback(t, 'dashboard.upcomingEvents.emptyAction', 'Schedule an event')}\n </Button>\n }\n />\n ) : (\n <div className=\"space-y-2\">\n {events.map((event) => (\n <button\n key={event.id}\n type=\"button\"\n className=\"w-full flex items-start gap-3 p-2 rounded-md hover:bg-accent/5 cursor-pointer transition-colors text-left\"\n onClick={() => navigate('/calendar')}\n >\n <div className=\"rounded-md bg-accent-pink-subtle text-accent-pink p-1.5 shrink-0\">\n <Clock className=\"size-3.5\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <p className=\"text-sm font-medium text-text-primary truncate\">{event.title}</p>\n <p className=\"text-xs text-text-secondary\">{formatEventDate(t, event.startTime)}</p>\n </div>\n {event.allDay && (\n <Badge variant=\"secondary\" className=\"text-[10px] shrink-0\">\n {tWithFallback(t, 'dashboard.upcomingEvents.allDay', 'All day')}\n </Badge>\n )}\n </button>\n ))}\n </div>\n )}\n </WidgetWrapper>\n );\n}\n"],"mappings":";;;;;;;AAQA,SAAS,EAAgB,GAA0B,GAAyB;CAC1E,IAAM,IAAI,IAAI,KAAK,EAAQ,EACrB,oBAAM,IAAI,MAAM,EAChB,IAAU,EAAE,cAAc,KAAK,EAAI,cAAc,EACjD,IAAW,IAAI,KAAK,EAAI;AAC9B,GAAS,QAAQ,EAAS,SAAS,GAAG,EAAE;CACxC,IAAM,IAAa,EAAE,cAAc,KAAK,EAAS,cAAc;AAU/D,QARI,IACK,EAAc,GAAG,kCAAkC,mBAAmB,EAC3E,MAAM,EAAE,mBAAmB,EAAE,EAAE;EAAE,MAAM;EAAW,QAAQ;EAAW,CAAC,EACvE,CAAC,GACA,IACK,EAAc,GAAG,qCAAqC,sBAAsB,EACjF,MAAM,EAAE,mBAAmB,EAAE,EAAE;EAAE,MAAM;EAAW,QAAQ;EAAW,CAAC,EACvE,CAAC,GACG,EAAE,mBAAmB,EAAE,EAAE;EAC9B,OAAO;EACP,KAAK;EACL,MAAM;EACN,QAAQ;EACT,CAAC;;AAGJ,SAAgB,EAAqB,EAAE,kBAAe,cAAW,eAAyB;CACxF,IAAM,EAAE,SAAM,GAAS,EACjB,IAAS,GAAe,kBAAkB,EAAE;AAElD,QACE,kBAAC,GAAD;EACE,OAAO,EAAc,GAAG,kCAAkC,kBAAkB;EAC5E,MAAM,kBAAC,GAAD,EAAU,WAAU,UAAW,CAAA;EACrC,MAAK;EACM;EACX,aAAa,CAAC;EACd,cACE,kBAAC,GAAD;GAAQ,SAAQ;GAAQ,MAAK;GAAK,WAAU;GAAU,eAAe,EAAS,YAAY;aAA1F;IACG,EAAc,GAAG,oCAAoC,WAAW;IAAE;IACnE,kBAAC,GAAD,EAAY,WAAU,eAAgB,CAAA;IAC/B;;YAGV,EAAO,WAAW,IACjB,kBAAC,GAAD;GACE,cAAa;GACb,OAAO,EAAc,GAAG,uCAAuC,qBAAqB;GACpF,WAAU;GACV,QACE,kBAAC,GAAD;IAAQ,SAAQ;IAAU,MAAK;IAAK,eAAe,EAAS,YAAY;cACrE,EAAc,GAAG,wCAAwC,oBAAoB;IACvE,CAAA;GAEX,CAAA,GAEF,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAO,KAAK,MACX,kBAAC,UAAD;IAEE,MAAK;IACL,WAAU;IACV,eAAe,EAAS,YAAY;cAJtC;KAME,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD,EAAO,WAAU,YAAa,CAAA;MAC1B,CAAA;KACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,KAAD;OAAG,WAAU;iBAAkD,EAAM;OAAU,CAAA,EAC/E,kBAAC,KAAD;OAAG,WAAU;iBAA+B,EAAgB,GAAG,EAAM,UAAU;OAAK,CAAA,CAChF;;KACL,EAAM,UACL,kBAAC,GAAD;MAAO,SAAQ;MAAY,WAAU;gBAClC,EAAc,GAAG,mCAAmC,UAAU;MACzD,CAAA;KAEH;MAjBF,EAAM,GAiBJ,CACT;GACE,CAAA;EAEM,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"VibesWidget.js","names":[],"sources":["../../../../src/pages/dashboard/widgets/VibesWidget.tsx"],"sourcesContent":["import { Sparkles, ArrowRight, Plus } from 'lucide-react';\nimport { Button, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport type { I18nContextValue } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { WidgetWrapper } from '../WidgetWrapper';\nimport { tWithFallback } from '../../../utils/i18n';\nimport type { WidgetProps } from '../types';\n\nfunction formatRelative(t: I18nContextValue['t'], dateString: string): string {\n const diffMs = Date.now() - new Date(dateString).getTime();\n const diffMin = Math.floor(diffMs / 60000);\n if (diffMin < 1) return tWithFallback(t, 'dashboard.vibes.justNow', 'just now');\n if (diffMin < 60)\n return tWithFallback(t, 'dashboard.vibes.minutesAgo', '{count}m ago', { count: diffMin });\n const diffHr = Math.floor(diffMin / 60);\n if (diffHr < 24)\n return tWithFallback(t, 'dashboard.vibes.hoursAgo', '{count}h ago', { count: diffHr });\n const diffDay = Math.floor(diffHr / 24);\n if (diffDay < 7)\n return tWithFallback(t, 'dashboard.vibes.daysAgo', '{count}d ago', { count: diffDay });\n return new Date(dateString).toLocaleDateString(undefined, {\n month: 'short',\n day: 'numeric',\n });\n}\n\nexport function VibesWidget({ workspaceData, isLoading, navigate }: WidgetProps) {\n const { t } = useI18n();\n const vibes = workspaceData?.recentVibes ?? [];\n\n return (\n <WidgetWrapper\n title={tWithFallback(t, 'dashboard.vibes.title', 'Recent Vibes')}\n icon={<Sparkles className=\"size-4\" />}\n size=\"medium\"\n isLoading={isLoading}\n unavailable={!workspaceData}\n headerAction={\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"text-xs\"\n onClick={() => navigate('/vibecontrols/vibes')}\n >\n {tWithFallback(t, 'dashboard.vibes.viewAll', 'View all')}{' '}\n <ArrowRight className=\"ml-1 size-3\" />\n </Button>\n }\n >\n {vibes.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-vibes\"\n title={tWithFallback(t, 'dashboard.vibes.emptyTitle', 'No vibes yet')}\n className=\"py-4\"\n action={\n <Button variant=\"outline\" size=\"sm\" onClick={() => navigate('/vibecontrols/vibes/new')}>\n <Plus className=\"mr-1.5 size-3.5\" />{' '}\n {tWithFallback(t, 'dashboard.vibes.emptyAction', 'Create your first vibe')}\n </Button>\n }\n />\n ) : (\n <ul className=\"space-y-1.5\">\n {vibes.slice(0, 5).map((vibe) => (\n <li key={vibe.id}>\n <button\n type=\"button\"\n onClick={() => navigate(`/vibecontrols/vibes/${vibe.id}`)}\n className=\"flex w-full items-center justify-between gap-2 rounded-md p-2 text-left transition-colors hover:bg-accent/5\"\n >\n <span className=\"truncate text-sm font-medium text-text-primary\">{vibe.name}</span>\n <span className=\"shrink-0 text-[11px] text-text-secondary\">\n {formatRelative(t, vibe.updatedAt)}\n </span>\n </button>\n </li>\n ))}\n </ul>\n )}\n </WidgetWrapper>\n );\n}\n"],"mappings":";;;;;;;AAQA,SAAS,EAAe,GAA0B,GAA4B;CAC5E,IAAM,IAAS,KAAK,KAAK,GAAG,IAAI,KAAK,EAAW,CAAC,SAAS,EACpD,IAAU,KAAK,MAAM,IAAS,IAAM;AAC1C,KAAI,IAAU,EAAG,QAAO,EAAc,GAAG,2BAA2B,WAAW;AAC/E,KAAI,IAAU,GACZ,QAAO,EAAc,GAAG,8BAA8B,gBAAgB,EAAE,OAAO,GAAS,CAAC;CAC3F,IAAM,IAAS,KAAK,MAAM,IAAU,GAAG;AACvC,KAAI,IAAS,GACX,QAAO,EAAc,GAAG,4BAA4B,gBAAgB,EAAE,OAAO,GAAQ,CAAC;CACxF,IAAM,IAAU,KAAK,MAAM,IAAS,GAAG;AAGvC,QAFI,IAAU,IACL,EAAc,GAAG,2BAA2B,gBAAgB,EAAE,OAAO,GAAS,CAAC,GACjF,IAAI,KAAK,EAAW,CAAC,mBAAmB,KAAA,GAAW;EACxD,OAAO;EACP,KAAK;EACN,CAAC;;AAGJ,SAAgB,EAAY,EAAE,kBAAe,cAAW,eAAyB;CAC/E,IAAM,EAAE,SAAM,GAAS,EACjB,IAAQ,GAAe,eAAe,EAAE;AAE9C,QACE,kBAAC,GAAD;EACE,OAAO,EAAc,GAAG,yBAAyB,eAAe;EAChE,MAAM,kBAAC,GAAD,EAAU,WAAU,UAAW,CAAA;EACrC,MAAK;EACM;EACX,aAAa,CAAC;EACd,cACE,kBAAC,GAAD;GACE,SAAQ;GACR,MAAK;GACL,WAAU;GACV,eAAe,EAAS,sBAAsB;aAJhD;IAMG,EAAc,GAAG,2BAA2B,WAAW;IAAE;IAC1D,kBAAC,GAAD,EAAY,WAAU,eAAgB,CAAA;IAC/B;;YAGV,EAAM,WAAW,IAChB,kBAAC,GAAD;GACE,cAAa;GACb,OAAO,EAAc,GAAG,8BAA8B,eAAe;GACrE,WAAU;GACV,QACE,kBAAC,GAAD;IAAQ,SAAQ;IAAU,MAAK;IAAK,eAAe,EAAS,0BAA0B;cAAtF;KACE,kBAAC,GAAD,EAAM,WAAU,mBAAoB,CAAA;KAAC;KACpC,EAAc,GAAG,+BAA+B,yBAAyB;KACnE;;GAEX,CAAA,GAEF,kBAAC,MAAD;GAAI,WAAU;aACX,EAAM,MAAM,GAAG,EAAE,CAAC,KAAK,MACtB,kBAAC,MAAD,EAAA,UACE,kBAAC,UAAD;IACE,MAAK;IACL,eAAe,EAAS,uBAAuB,EAAK,KAAK;IACzD,WAAU;cAHZ,CAKE,kBAAC,QAAD;KAAM,WAAU;eAAkD,EAAK;KAAY,CAAA,EACnF,kBAAC,QAAD;KAAM,WAAU;eACb,EAAe,GAAG,EAAK,UAAU;KAC7B,CAAA,CACA;OACN,EAXI,EAAK,GAWT,CACL;GACC,CAAA;EAEO,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"WelcomeWidget.js","names":[],"sources":["../../../../src/pages/dashboard/widgets/WelcomeWidget.tsx"],"sourcesContent":["import { LayoutDashboard, Building2, FolderKanban, Users, Activity } from 'lucide-react';\nimport { GlassCard, GradientText } from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { WidgetWrapper } from '../WidgetWrapper';\nimport { tWithFallback } from '../../../utils/i18n';\nimport type { WidgetProps } from '../types';\n\nfunction StatCard({\n icon,\n label,\n value,\n onClick,\n}: {\n icon: React.ReactNode;\n label: string;\n value: number | string;\n onClick?: () => void;\n}) {\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onClick?.();\n }\n };\n\n return (\n <GlassCard\n treatment=\"metric\"\n className={`p-3${onClick ? ' cursor-pointer hover:shadow-[var(--shadow-pop-hover)] hover:border-border-strong focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]' : ''}`}\n onClick={onClick}\n onKeyDown={onClick ? handleKeyDown : undefined}\n role={onClick ? 'button' : undefined}\n tabIndex={onClick ? 0 : undefined}\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"rounded-md bg-action-primary-bg/10 p-2 text-primary\">{icon}</div>\n <div>\n <p className=\"text-2xl font-bold text-text-primary tabular-nums\">{value}</p>\n <p className=\"text-xs text-text-muted\">{label}</p>\n </div>\n </div>\n </GlassCard>\n );\n}\n\nexport function WelcomeWidget({ workspaceData, globalData, isLoading, navigate }: WidgetProps) {\n const { t } = useI18n();\n const user = globalData?.currentUser;\n const firstName =\n user?.firstName ||\n user?.name?.split(' ')[0] ||\n tWithFallback(t, 'dashboard.welcome.fallbackName', 'there');\n const hour = new Date().getHours();\n const greeting =\n hour < 12\n ? tWithFallback(t, 'dashboard.welcome.greetingMorning', 'Good morning')\n : hour < 18\n ? tWithFallback(t, 'dashboard.welcome.greetingAfternoon', 'Good afternoon')\n : tWithFallback(t, 'dashboard.welcome.greetingEvening', 'Good evening');\n\n const totalWorkspaces = workspaceData?.workspaces.total ?? 0;\n const totalProjects = workspaceData?.projects.total ?? 0;\n const totalGroups = workspaceData?.groups.totalCount ?? 0;\n const activityCount = workspaceData?.recentActivities.length ?? 0;\n\n return (\n <WidgetWrapper\n title={tWithFallback(t, 'dashboard.welcome.title', 'Dashboard')}\n icon={<LayoutDashboard className=\"size-4\" />}\n size=\"large\"\n isLoading={isLoading}\n >\n <div className=\"space-y-4\">\n <GlassCard treatment=\"glass\" glow className=\"relative overflow-hidden p-4\">\n <h2 className=\"text-xl font-bold text-text-primary\">\n {greeting}, <GradientText>{firstName}</GradientText>!\n </h2>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tWithFallback(\n t,\n 'dashboard.welcome.subtitle',\n \"Here's what's happening across your workspaces today.\"\n )}\n </p>\n </GlassCard>\n <div className=\"grid grid-cols-2 sm:grid-cols-4 gap-3\">\n <StatCard\n icon={<Building2 className=\"size-4\" />}\n label={tWithFallback(t, 'dashboard.welcome.statWorkspaces', 'Workspaces')}\n value={totalWorkspaces}\n onClick={() => navigate('/workspaces/list')}\n />\n <StatCard\n icon={<FolderKanban className=\"size-4\" />}\n label={tWithFallback(t, 'dashboard.welcome.statProjects', 'Projects')}\n value={totalProjects}\n onClick={() => navigate('/workspaces/projects')}\n />\n <StatCard\n icon={<Users className=\"size-4\" />}\n label={tWithFallback(t, 'dashboard.welcome.statGroups', 'Groups')}\n value={totalGroups}\n onClick={() => navigate('/groups')}\n />\n <StatCard\n icon={<Activity className=\"size-4\" />}\n label={tWithFallback(t, 'dashboard.welcome.statRecentActivity', 'Recent Activity')}\n value={activityCount}\n onClick={() => navigate('/workspaces/activity')}\n />\n </div>\n </div>\n </WidgetWrapper>\n );\n}\n"],"mappings":";;;;;;;AAOA,SAAS,EAAS,EAChB,SACA,UACA,UACA,cAMC;AAQD,QACE,kBAAC,GAAD;EACE,WAAU;EACV,WAAW,MAAM,IAAU,mLAAmL;EACrM;EACT,WAAW,KAZQ,MAA2B;AAChD,IAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,SACjC,EAAE,gBAAgB,EAClB,KAAW;MAS0B,KAAA;EACrC,MAAM,IAAU,WAAW,KAAA;EAC3B,UAAU,IAAU,IAAI,KAAA;YAExB,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cAAuD;IAAW,CAAA,EACjF,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;IAAG,WAAU;cAAqD;IAAU,CAAA,EAC5E,kBAAC,KAAD;IAAG,WAAU;cAA2B;IAAU,CAAA,CAC9C,EAAA,CAAA,CACF;;EACI,CAAA;;AAIhB,SAAgB,EAAc,EAAE,kBAAe,eAAY,cAAW,eAAyB;CAC7F,IAAM,EAAE,SAAM,GAAS,EACjB,IAAO,GAAY,aACnB,IACJ,GAAM,aACN,GAAM,MAAM,MAAM,IAAI,CAAC,MACvB,EAAc,GAAG,kCAAkC,QAAQ,EACvD,qBAAO,IAAI,MAAM,EAAC,UAAU,EAC5B,IACJ,IAAO,KACH,EAAc,GAAG,qCAAqC,eAAe,GACrE,IAAO,KACL,EAAc,GAAG,uCAAuC,iBAAiB,GACzE,EAAc,GAAG,qCAAqC,eAAe,EAEvE,IAAkB,GAAe,WAAW,SAAS,GACrD,IAAgB,GAAe,SAAS,SAAS,GACjD,IAAc,GAAe,OAAO,cAAc,GAClD,IAAgB,GAAe,iBAAiB,UAAU;AAEhE,QACE,kBAAC,GAAD;EACE,OAAO,EAAc,GAAG,2BAA2B,YAAY;EAC/D,MAAM,kBAAC,GAAD,EAAiB,WAAU,UAAW,CAAA;EAC5C,MAAK;EACM;YAEX,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD;IAAW,WAAU;IAAQ,MAAA;IAAK,WAAU;cAA5C,CACE,kBAAC,MAAD;KAAI,WAAU;eAAd;MACG;MAAS;MAAE,kBAAC,GAAD,EAAA,UAAe,GAAyB,CAAA;;MACjD;QACL,kBAAC,KAAD;KAAG,WAAU;eACV,EACC,GACA,8BACA,wDACD;KACC,CAAA,CACM;OACZ,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MACE,MAAM,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA;MACtC,OAAO,EAAc,GAAG,oCAAoC,aAAa;MACzE,OAAO;MACP,eAAe,EAAS,mBAAmB;MAC3C,CAAA;KACF,kBAAC,GAAD;MACE,MAAM,kBAAC,GAAD,EAAc,WAAU,UAAW,CAAA;MACzC,OAAO,EAAc,GAAG,kCAAkC,WAAW;MACrE,OAAO;MACP,eAAe,EAAS,uBAAuB;MAC/C,CAAA;KACF,kBAAC,GAAD;MACE,MAAM,kBAAC,GAAD,EAAO,WAAU,UAAW,CAAA;MAClC,OAAO,EAAc,GAAG,gCAAgC,SAAS;MACjE,OAAO;MACP,eAAe,EAAS,UAAU;MAClC,CAAA;KACF,kBAAC,GAAD;MACE,MAAM,kBAAC,GAAD,EAAU,WAAU,UAAW,CAAA;MACrC,OAAO,EAAc,GAAG,wCAAwC,kBAAkB;MAClF,OAAO;MACP,eAAe,EAAS,uBAAuB;MAC/C,CAAA;KACE;MACF;;EACQ,CAAA"}
|